[Java] JUnit5
우테코 2주차 미션에서 단위 테스트를 짜야하는 요구조건이 나왔다.
우선 개괄적인 이해를 위해 테코톡 영상을 시청하며 정리해본다.
필수 참고 사이트
1. JUnit?
- 단위 테스트 프레임워크
JUnit5
- JUnit Platform 위에 Jupiter API를 통해 구동
- Platform : 테스트를 실행시켜주는 런처 제공. TestEngine API 제공
- Jupiter : JUnit5를 지원하는 TestEngine API 구현체
2. Annotations
2-1. @Test
annot.
- 테스트 메서드라는 것을 나타내는 어노테이션
- JUnit4와 다르게 어떠한 속성도 선언하지 않는다.
// JUnit4
@Test(expected = Exception.class)
void create() throws Exception {
...
}
// JUnit5
@Test
void create() {
...
}
2-2. 생명주기(LifeCycle) annot.
@BeforeAll
: 해당클래스
에 위치한 모든 테스트 메서드 실행 전에 딱 한 번 실행되는 메서드@AfterAll
: 해당클래스
에 위치한 모든 테스트 메서드 실행 후에 딱 한 번 실행되는 메서드
@BeforeEach
: 해당클래스
에 위치한 모든 테스트 메서드 실행 전에 실행되는 메서드@AfterEach
: 해당클래스
에 위치한 모든 테스트 메서드 실행 후에 실행되는 메서드- 매 테스트 메서드마다 새로운 클래스를 생성(new)하여 실행 -> 비효율적!
BeforeAll vs. BeforeEach
@BeforeAll
@Test
@Test
@Test
------
@BeforeEach
@Test
@BeforeEach
@Test
@BeforeEach
@Test
- 단위 테스트들이 @BeforeAll 선언 이후 조건들에 대해 어떠한 변경도 하지 않는다!
- BeforeAll
- 단위 테스트가 조건에 영향을 미친다!
- BeforeEach
- 테스트 매 실행시마다 초기화
2-3. @Disabled
annot.
- 테스트를 하고 싶지 않은 클래서나 메서드에 붙이는 어노테이션
class Example {
@Test
@Disabled("문제가 해결될 때까지 테스트 중단")
void test() {
System.out.println("테스트1")
}
@Test
void test() {
System.out.println("테스트2")
}
}
2-4. @DisplayName
annot.
- 어떤 테스트인지 쉽게 표현할 수 있도록 해줌
- 공백, Emoji, 특수문자 등을 모두 지원
@DisplayName("테스트 이름 정해")
class Example {
@Test
@DisplayName("우아한 테스트")
void test() {
}
}
2-5. @RepeatedTest
annot.
- 특정 테스트를 반복시키고 싶을 때 사용
- ex. 성능적인 이슈를 확인할 때
- 반복 횟수와 반복 테스트 이름 설정 가능
@RepeatedTest(10)
@DisplayName("반복 테스트1")
void repeatedTest() {
}
@RepeatedTest(value = 10, name = '{displayName} 중 {currentRepetition} of {totalReptitions}')
@DisplayName("반복 테스트2")
void repeatedTest() {
}
2-6. @ParameterizedTest
annot.
- 테스트에 여러 다른 매개변수를 대입해가며 반복 실행할 때 사용
- 반복문 사용보다 가독성 향상
- 테스트 성격, 목적 명료화 가능
@ParameterizedTest
@CsvSource(value = {"ACE,ACE:12", "ACE,ACE,ACE:13", "ACE,ACE,TEN:12"}, delimeter = ':')
@DisplayName("에이스 카드가 여러 개일 때 합 구하기")
void calculateCardSumWhenAceIsTwo(final String input, final int expected) {
for (final String number : inputs) {
final CardNumber cardNumber = CardNumber.valueOf(number)
dealer.receiveOneCard(new Card(cardNumber, CardType.CLOVER));
}
assertThat(dealer.calculateScore()).isEqaulTo(expected);
}
2-7. @Nested
annot.
- 테스트 클래스 안에서 내부 클래스를 정의해 테스트를 계층화 할 때 사용
- 내부클래스는 부모클래스의 멤버 필드에 접근 가능
- Before / After와 같은 생명주기 테스트에 관계된 메소드들도 계층에 맞춰 동작
public class DisplayNameTest {
@Test
public void testAsuccess() { /* */ }
@Test
public void testAfail() { /* */ }
@Test
public void test1success() { /* */ }
@Test
public void test1success() { /* */ }
@Test
public void test2success() { /* */ }
@Test
public void test2fail() { /* */}
}
아래와 같이 UnitTest 리팩터링 가능
public class DisplayNameTest {
@Nested
class testA {
@Test
public void success() { /* */ }
@Test
public void fail() { /* */ }
}
@Nested
class testNumber {
@Nested
class test1 {
@Test
public void success() { /* */ }
@Test
public void fail() { /* */ }
}
@Nested
class test2 {
@Test
public void success() { /* */ }
@Test
public void fail() { /* */ }
}
}
}
3. Assertions
- 사전적 의미 : 주장, 행사, 단정문
- 테스트 케이스의 수행 결과를 판별하는 메서드
- 모든 JUnit Jupiter Assertions는
statc
메서드
(단, 아래는 JUnit5에서 추가된 몇가지 사항)
3-1. assertAll(executables…)
- 매개변수로 받는 모든 테스트코드를 한 번에 실행
- 오류가 나도 끝까지 실행한 뒤 한 번에 모아서 출력
List<String> owners = Arrays.asList("Betty Davis", "Eduardo Rodriquez");
// assert
assertAll(
() -> assertTrue(owners.contains("Betty Doe"), "Contains Betty Doe"),
() -> assertTrue(owners.contains("John Doe"), "Contains John Doe"),
() -> assertTrue(owners.contains("Eduardo Rodriquez"), "Eduardo Rodriquez")
);
3-2. assertThrows(expectedType, executable)
- 예외 발생을 확인하는 테스트
- executable의 로직이 실행되는 도중 expectedType의 에러를 발생시키는지 확인
//JUnit4
//예외가 발생하는지 여부만 검증 가능
@Test(expected = Exception.class)
void create() throws Exception {
...
}
// JUnit5
// 예외 발생 검증 + 예외 상태 검증
@Test
void exceptionThrow {
Exception e = assertThrows(Exception.class,() -> new Test(-10));
assertDoesNotThrow(() -> System.out.println("Do Something"));
}
3-3. assertTimeout(duration, executable)
- 특정 시간 안에 실행이 완료되는지 확인
- Duration : 원하는 시간
- Executable : 테스트 할 로직
@Test
@DisplayName("타임아웃 준수")
void assert_timeout1() {
assertTimeout(Duration.ofMillis(100), () -> {
new Study(10, 10);
Thread.sleep(300);
});
void assert_timeout2() {
assertTimeout(Duration.ofMinutes(2), () -> {
new Study(10, 10);
Thread.sleep(10);
});
}
4. Assumption
- 전제문이 ture라면 실행, false라면 종료
assumeTure
: false일 때 이후 테스트 전체가 실행되지 않음assumingThat
: 파라미터로 전달된 코드블럭만 실행되지 않음- CI 등 특정 환경에서만 테스트를 진행해야 하는 경우 사용 가능
void dev_env_only() {
assumeTrue("DEV".equals(System.getenv("ENV")), () -> "개발 환경이 아닙니다.");
assertEqauls("A", "A"); //위에가 false라면, 단정문이 실행되지 않음
void some_test() {
assumingThat("DEV".equals(System.getenv("ENV")),
() -> {
assertEquals("A", "B"); //단정문이 실행되지 않음
});
assertEqauls("A", "A"); //단정문이 실행됨
}