1. JUnit의 핵심
테스트 클래스가 되는 조건은 두가지이다. - public 클래스여야 한다. - 파라미터를 받지 않는 생성자를 제공해야 한다.
테스트 메소드가 되기 위한 조건은 4가지 이다. - @Test 어노테이션이 부여되어야 한다. - public 메서드여야 한다. - 파라미터를 받지 않아야 한다. - 반환형은 void 여야 한다. |
import static org.junit.Assert.assertEquals; import org.junit.Test;
public class CalculatorTest { @Test public oid testAdd() { Calculator calculator = new Calculator(); double result = calculator.add(1, 1);
assertEquals(2, result, 0); } } |
- JUnit은 각 @Test메서드를 호출할 때마다 테스트 클래스의 인스턴스를 새로 생성한다.
- 테스트 메서드들을 독립된 메모리 공간에서 실행시킴으로써, 혹시 모를 의도치 않은 부작용을 방지하기 위함이다.
- 테스트 검증에는 JUnit의 Assert 클래스에 정의된 assert 메서드를 사용한다.
- assert 메서드
assert 메서드설명
assertArrayEquals("message", A, B) | 배열 A와 B가 일치함을 확인한다. |
assertEquals("message", A, B) | 객체 A와 B가 일치함을 확인한다. B를 파라미터로 A의 equals() 메서드를 호출한다.(A.equals(B)) |
assertSame("message", A, B) | 객체 A와 B가 같은 객체임을 확인한다. assertEquals 메서드는 두 객체의 값이 같은가를 검사하는데 반해(equals 메서드 사용), assertSame 메서드는 두 객체가 동일한, 즉 하나의 객체인가를 검사한다. |
assertTrue("message", A) | 조건 A가 참(true)임을 확인한다. |
assertNotNull("message", A) | 객체 A가 null이 아님을 확인한다. |
- 여러개의 테스트 클래스를 동시에 실행해야 할 때는, 테스트 스위트라 불리는 또 다른 객체를 생성한다.
- 테스트 스위트는 특수한 형태의 테스트 러너로, 테스트 클래스와 똑같은 방식으로 실행할 수 있다.
- 테스트 클래스와 스위트, 러너의 동작 방식을 이해한다면 어떠한 클래스도 거뜬히 작성해내는 경지에 오를 것이다.
- 이 세 객체는 JUnit 프레임워크를 지탱하는 척추에 비유될 수 있다.
- JUnit 핵심 객체
JUnit 개념역할
Assert | 테스트하려는 조건을 명시한다. assert 메서드는 조건이 만족되면 아무 일도 없었다는 듯이 조용히 지나가며, 만족하지 못하면 얘외를 던진다. |
Test | @Test 어노테이션이 부여된 메서드로, 하나의 테스트를 뜻한다. Junit은 먼저 메서드를 포함하는 클래스의 인스턴스를 만들고, 어너테이션된 메서드를 찾아 호출한다. |
Test 클래스 | @Test 메서드를 포함한 클래스이다. |
Suite | 스위트는 여러 테스트 클래스를 하나로 묶는 수단을 제공한다. |
Runner | 러너는 테스트를 실행시킨다. |
파라메터화(parameterized) 테스트
// 1. @RunWith(value=Parameterized.class) public class ParameterizedTest {
// 2. private double expected; private double valueOne; private double valueTwo;
// 3. @Parameters public static Collection<Integer[]> getTestParameters() { return Arrays.asList(new Integer[][] { {2, 1, 1}, // 예상 값, 값 1, 값 2 {3, 2, 1}, // 예상 값, 값 1, 값 2 {4, 3, 1} // 예상 값, 값 1, 값 2 } ); }
// 4. public ParameterizedTest(double expected, double valueOne, double valueTwo) { this.expected = expected; this.valueOne = valueOne; this.valueTwo = valueTwo; }
// 5. @Test public void sum() { // 6. Calculator cal = new Calculator();
// 7. assertEquals(expected, cal.add(valueOne, valueTwo), 0); } } |
- 파라메터화 테스트 러너를 사용하려면 다음 조건을 만족시켜야 한다.
- 테스트 클래스에는 반드시 @RunWith 어노테이션을 부여해야 하며, 그 파라미터로는 Parameterized 클래스를 사용한다.
- 테스트에 사용될 값을 인스턴스 변수로 선언하고
- @Parameters라 표시된 메서드가 하나 필요하다.
- @Paramters public static java.util.Collection 이어야 한다.
- 어떠한 파라미터도 입력 받아서는 안된다.
- Collection의 원소는 배열(array)이고, 길이는 모두 같아야 한다.
- 그 길이는 유일한 public 생성자가 받는 파라미터의 수와도 일치해야 한다.
- 테스트 메서드 sum()을 구현했다.
- sum 메서드는 Calculator 프로그램의 인스턴스를 생성하고,
- 앞서 제공된 파라미터들을 사용해 결과를 단언한다.
- JUnit 런타임 작동 방식
- 정적 메서드인 getTestParameters를 호출해 컬렉션 객체를 얻는다.
- 컬렉션에 저장된 배열의 수만큼 순환한다.
- JUnit은 유일한 public 생성자를 찾는다.
- 이때, public 생성자가 두 개 이상이면 AssertionError를 던진다.
- 이제 찾은 생성자에 배열의 원소를 파라미터로 넣어 호출한다.
- 첫번째 배열 {2, 1, 1}을 파라미터로 입력 파라미터 세 개짜리 생성자를 호출할 것이다.
- @Test 메서드를 호출한다.
"테스트는 두려움을 지루함으로 변화시켜주는 프로그래머의 돌이다." 켄트 백 : 테스트 주도 개발
부트스트랩 단계
import static org.junit.Assert.*; import org.junit.Test;
// 1. public class TestDefaultController { private DefaultController controller;
// 2. @Before public void instantiate() throws Exception { controller = new DefaultController(); }
// 3. @Test public void testMethod() { // 4. throw new RuntimeException("implement me"); } } |
- 테스트 클래스의 이름에는 Test라는 접두어를 붙인다.
- DefaultController 인스턴스를 만들기 위해 @Before 어노테이션 메서드를 이용한다.
- @Before 메서드는 각 테스트 메서드 사이에서 호출되는 JUnit의 기본 확장 포인트이다.
- 실행할 테스트 케이스가 하나도 없으면 안되므로 더미 테스트 메서드를 추가하였다.
- 이후에 테스트를 수행할 기반이 다 갖춰졌다는 확신이 서면, 그 때 진짜 테스트 메서드를 추가하면 된다.
- 이 테스트는 실행은 되지만, 실패하기 때문에 다음 단계는 테스트가 성공하도록 수정할 것이다.
- 구현이 덜 끝난 테스트 코드는 예외를 던져야 한다는 모범 사례를 따르도록 하자.
- 이를 통해 테스트가 통과하는 것을 막고, 구현할 게 아직 남아있음을 잊지 않을 수 있다.
@Before와 @After
- @Before와 @After 어노테이션이 부여된 메서드들은 매 @Test 메서드가 호출되기 바로 직전과 직후에 실행된다. - @Test 메서드의 성공 실패 여부는 상관없다. - 때문에 필요한 도메인 객체를 미리 생성해두거나, 특정 상태로 미리 셋팅하기 위한 공통 코드를 뽑아두기에 최적의 장소라 할 수 있다. - 원한다면 여러 개의 @Before와 @After 메서드를 정의할 수 있지만, 이들 사이의 실행 순서를 정하는 방법은 없으니 주의하기 바란다.
- @BeforeClass와 @AfterClass라는 어노테이션도 제공한다. - 클래스 안에 정의된 모든 @Test 메서드들을 수행하기 전과 수행한 후에 오직 한 번씩만 호출된다. - @BeforeClass와 @AfterClass 메서드도 원한다면 여러 개 정의할 수 있지만, 실행 순서는 정의되어 있지 않다.
- @Before/@After, @BeforeClass/@AfterClass 메서드 모두는 반드시 public이어야 하며, - @BeforeClass와 @AfterClass 메서드는 public이면서 동시에 static이여야 한다. |
@Test
- 테스트 메서드에 의미 있는 이름을 부여하라. - assert 호출 시에는 실패 원인을 기술하라. - 하나의 @Test 메서드에서는 하나의 테스트만 수행하라. - 실패할 가능성이 있는 모든 것을 테스트하라. - 테스트를 통해 코드를 개선하라. - 예외 테스트도 읽기 쉽게 만들어라. - 테스트를 건너뛸 때는 반드시 그 이유를 명시하라.
- @Test(expected=SomeExcception.class) : 테스트 목적이 예외 상황 테스트이고, 발생되길 기대하는 예외가 있으니, 그 예외가 무엇인지 명시할 필요가 있다. - @Test(timeout=130) : 주어진 시간 내에 완료되지 못한 테스트는 실패라고 판단하며, 밀리초 단위로 제한 시간을 설정할 수 있다. - @Ignore(value="테스트 제외 이유 설명") : 때로는 일부 테스트들은 건너뛰는 것이 좋을 때도 있다. |
햄크레스트 (Hamcrest)
-
수 많은 유용한 매처(matcher) 객체(제약이나 술어라 불린다)를 제공하며, 다양한 언어로 포팅되어 있다.
-
테스트 프레임워크는 아니고, 그 보다는 간단한 매칭 규칙을 선언적으로 정의할 때 큰 도움이 된다.
public class HamcrestTest { private List<String> values;
@Before public void setUpList() { values = new ArrayList<String>(); values.add("x"); values.add("y"); values.add("z"); }
@Test public void testWithoutHamcrest() { assertTrue(values.contains("one") || values.contains("two") || values.contains("three")); } } |
import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.Corematchers.equalTo; import static org.junit.JUnitMatchers.hasItem;
public class HamcrestTest { private List<String> values;
@Before public void setUpList() { values = new ArrayList<String>(); values.add("x"); values.add("y"); values.add("z"); }
@Test public void testWithHamcrest() { assertTrue(values, hasItem(anyOf(equalTo("one"), equalTo("two"), equalTo("three")))); } } |
-
매처의 강력한 기능 중 하나가 서로 다른 매처를 조합해 사용하는 능력이다.
-
assert 코드에 Hamcrest 매처를 사용할지 말지는 순전히 개인 취향이다.
-
표준 assert 매커니즘 대비 Hamcrest가 제공하는 독특한 장점은, assert가 실패했을 때 사람이 읽을 수 있는 형태의 설명을 제공한다는 것이다.
-
가장 널리 쓰이는 Hamcrest 매처
코어(core)의미
anything | 무엇이든 상관없이 모든 것을 가리킴. assert 문의 가독성을 높이고 싶을 때 유용하다. |
is | 문장 가독성 향상 목적으로만 사용된다. |
allOf | 포함한 모든 매처가 매칭되는지 검사한다(&& 연산자와 동일). |
anyOf | 포함한 매처 중 어느 하나라도 매치되는 것이 있는지 검사한다( |
not | 포함한 매처들의 의미를 부정한다(! 연산자와 동일) |
instanceOf, isCompatibleType | 객체들이 호환 가능한 타입인지 확인한다. |
sameInstance | 객체 신원을 확인한다. |
notNullValue, nullValue | 값이 null인지(혹은 null 아닌지) 검사한다. |
hasProperty | 자바빈이 특정 속성을 갖는지 검사한다. |
hasEntry, hasKey, hasValue | 주어진 Map이 명시된 entry, key, value를 포함하는지 검사한다. |
hasItem, hasItems | 주어진 컬렉션이 명시한 아이템, 혹은 아이템들을 포함하는지 검사한다. |
closeTo, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEquals | 주어진 숫자가 또 다른 숫자에 근접한지, 더 큰지, 더 크거나 같은지, 더 작은지, 더 작거나 같은지 검사한다. |
equalToIgnoringCase | 주어진 문자열이 다른 문자열과 일치하는지 검사한다(대소문자 무시). |
equalToIgnoringWhiteSpace | 주어진 문자열이 다른 문자열과 일치하는지 검사한다(공백문자 무시). |
containsString, endsWith, startWith | 주어진 문자열이 다른 문자열을 포함하는지, 그 문자열로 시작하거나 끝나는지 검사한다. |
단위 테스트가 필요한 이유
-
기능 테스트보다 훨씬 높은 테스트 커버리지 달성이 가능하다.
-
팀 생산성을 향상시킨다.
-
회귀 테스트(regression test)를 수행하고 디버깅의 필요성을 줄여준다.
-
리팩터링과 코드 수정 시 올바로 하고 있다는 확신을 준다.
-
구현 품질을 향상시킨다.
-
기대하는 행위를 문서화한다.
-
코드 커버리지 등 각종 측정을 가능하게 한다.
테스트의 종류
-
통합 테스트(integration test)
-
기능 테스트(functional test)
-
스트레스 테스트(stress test)와 부하 테스트(load test)
-
인수 테스트(acceptance test)
단위 테스트의 세 가지 맛
-
논리 단위 테스트 : 한 메서드에 집중한 테스트, 목 객체(Mock Object)나 스텁(Stub)을 이용해 테스트 메서드의 경계를 제어할 수 있다.
-
통합 단위 테스트 : 실제 운영 환경(혹은 그 일부)에서 컴포넌트 간 연동에 치중한 테스트, 예를 들어 데이터베이스를 사용하는 코드라면 데이터 베이스를 효과적으로 호출하는가를 테스트할 수 있다.
-
기능 단위 테스트 : 자극 반응(Stimulus Response)을 확인하기 위해 통합 단위 테스트의 경계를 확장한 테스트. 예를 들어 인증된 클라이언트만 접근 가능한 보안 웹 페이지를 가진 웹 애플리케이션을 가정해보자. 만약 로그인을 하지 않은 채 보안 페이지에 접근하면, 클라이언트를 로그인 페이지로 돌려보내야 한다. 이 상황을 검사하려면, 기능 단위 테스트(Functional Unit Test)는 이 페이지에 접속하려는 HTTP 요청을 보내고, 응답으로 재전송 코드(HTTP 상태 코드 302)가 오는지 확인하는 방법이 있다.
테스트 주도 개발 (Test-driven development, TDD)
-
자동화 테스트가 실패했을 때와 코드 중복을 제거하려 할 때에만 새로운 코드를 작성하도록 권하는 프로그래밍 실천법이다.
-
TDD의 목표는 '작동하는 깨끗한 코드(Clean code that works)'이다.
-
개발 주기 조정하기
-
전통적인 개발 주기는 코딩, 테스트, (반복), 커밋 순
-
TDD 실천자들은 테스트, 코드, (반복), 커밋 순
-
테스트가 설계를 이끌고 메서드의 첫 대상이 된다.
-
코드를 설계 > 동작 방식을 문서화 > 단위 테스트를 수행한다.
-
테스트 > 코드, 리팩터링, (반복), 커밋
-
지속적으로 회귀 테스트를 수행하라.
-
-
TDD 실천으로 가는 두 단계
-
새 코드를 작성하기 앞서 실패하는 자동화 테스트를 작성하라.
-
중복을 제거하라.
-
2. Unit Test 원칙
Unit Test 에서의 F.I.R.S.T 원칙 - 일반적으로 단위테스트 코드를 작성할 때 5가지 원칙을 강조한다.
F - Fast : 테스트 코드를 실행하는 일은 오래 걸리면 안된다. I - Independent/Isolated : 독립적으로 실행이 되어야 한다. 또는 고립시킨다. R - Repeatable : 반복 가능해야 한다. S - Self Validating : 스스로 검증 가능해야 한다. T - Timely : 바로 사용 가능해야 한다. |
3. 무엇을 테스트 할 것인가?
Right-BICEP - [Right]-BICEP : 결과가 올바른가? - Right-[B]ICEP : 경계 조건(boundary conditions)은 맞는가? - CORRRECT 기억법 - [C]onformance : 값이 기대한 양식을 준수하고 있는가? - [O]rdering : 값의 집합이 적절하게 정렬되거나 정렬되지 않았나? - [R]Range : 이성적인 최솟값과 최댓값 안에 있는가? - [R]eference : 코드 자체에서 통제할 수 없는 어떤 외부 참조를 포함하고 있는가? - [E]xistence : 값이 존재하는가? (널이 아니거나(non-null), 0이 아니거나(nonzero), 집합이 존재하는가 등)? - [C]ardinality : 정확히 충분한 값들이 있는가? - [T]ime : 모든 것이 순서대로 일어나는가? 정확한 시간에? 정시에? - Right-B[I]CEP : 역 관계(inverse relationship)를 검사할 수 있는가? - Right-BI[C]EP : 다른 수단을 활용하여 교차 검사(cross-check)할 수 있는가? - Right-BIC[E]P : 오류 조건(error conditions)을 강제로 일어나게 할 수 있는가? - Right-BICE[P] : 성능 조건(performance characteristics)은 기준에 부합하는가? |
'JAVA' 카테고리의 다른 글
[Java] Unit Test - 3.테스트 작성 (0) | 2020.02.06 |
---|---|
[Java] Unit Test - 2.테스트 개요 (0) | 2020.02.05 |
[JAVA] @Scheduled Cron 표현식 (0) | 2019.08.09 |
[Java] 날짜 시간 계산 하기 (0) | 2019.08.06 |
[Java] String, StringBuffer, StringBuilder (0) | 2019.07.17 |