JUnit5 훑어보기
이번 스터디 주제로 JUnit5에 대해 공부한 것을 정리해봤습니다.
JUnit5
자바 개발자가 가장 많이 사용하는 테스트 프레임워크입니다.
자바 8 버전 이상이 필요하며 크게 3개의 모듈로 구성되어 있습니다.
- JUnit Platform: 테스트를 실행해주는 런처와 TestEngine API를 제공
- JUnit Jupiter: JUnit5를 테스트할 수 있는 TestEngine API의 구현체
- JUnit Vintage: JUnit 3, 4를 지원하는 TestEngine API의 구현체
설치
스프링 부트는 JUnit을 기본 테스트 프레임워크로 채택하고 있기 때문에 프로젝트 생성 시 따로 설치를 할 필요가 없지만 스프링 레거시를 사용한다면 하단의 라이브러리 설치가 필요합니다.
메이븐
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
그래들
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
기본 어노테이션
- @Test : 테스트 메서드임을 명시
- @BeforeEach : 각각의 테스트 메서드가 실행되기 전에 실행되어야 하는 메서드를 명시
- @AfterEach : 각각의 테스트 메서드가 실행된 후 실행
- @BeforeAll : 테스트가 실행되기 전 단 한 번만 실행
- @AfterAll : 테스트가 완전히 끝난 후 단 한 번만 실행
- @Tag : 테스트를 필터링하기 위해 사용
- @Timeout : 테스트 메서드의 제한시간을 설정하며 시간 안에 테스트가 끝나지 않으면 실패
- @Disabled : 테스트를 비활성화
- @DisplayName : 테스트의 이름을 붙여줄 때 사용
Assertions
assertions는 테스트 코드를 작성하며 기대하는 값 또는 상황과 일치하는 지 검증하기 위해 사용합니다.
JUnit Jupiter는 기본 assertions를 내장하고 있고 org.junit.jupiter.api.Assertions
라이브러리로 제공합니다.
@Test
@DisplayName("스터디를 생성 시 인스턴스가 존재해야 한다.")
void create_study_is_not_null() {
Study study = new Study(10);
assertNotNull(study);
}
@Test
@DisplayName("스터디를 생성 시 DRAFT 상태여야 한다.")
void create_study_status_should_draft() {
Study study = new Study(10);
assertEquals(
StudyStatus.DRAFT,
study.getStatus(),
() -> "스터디를 처음 만들면 " + StudyStatus.DRAFT + " 상태이다.");
}
@Test
@DisplayName("스터디를 생성 시 DRAFT 상태여야 하며 인원수가 0보다 커야한다.")
void create_study_limit_greater_than_zero() {
Study study = new Study(10);
assertAll(
() -> assertEquals(StudyStatus.DRAFT, study.getStatus()),
() -> assertTrue(study.getLimit() > 0, "스터디 최대 참석 인원은 10명이다.")
);
}
@Test
@DisplayName("스터디 인원을 0보다 작게 설정 시 예외가 발생한다.")
void throw_exception_limit_less_than_zero() {
IllegalArgumentException exception =
assertThrows(IllegalArgumentException.class, () -> new Study(-10));
assertEquals("limit은 0보다 커야 한다.", exception.getMessage());
}
@Test
@DisplayName("스터디 인스턴스는 20ms 안에 생성되어야 한다.")
void create_study_duration_10ms() {
assertTimeout(Duration.ofMillis(20), () -> {
new Study(10);
Thread.sleep(50);
});
}
각 assertions 정적 메서드들은 String
또는 Supplier<String>
타입의 람다 인스턴스를 전달하여 메시지를 출력하도록 할 수 있습니다.
Assumptions
특정한 조건 또는 환경에서의 테스트를 작성하기 위한 방법으로 org.junit.jupiter.api.Assumptions
로 제공됩니다.
public class AssumptionTest {
@Test
void ci_env_test() {
assumeTrue("CI".equals(System.getenv("ENV")));
}
@Test
void dev_env_test() {
assumeTrue("DEV".equals(System.getenv("ENV")),
() -> "Aborting test: not on developer workstation");
}
@Test
void all_env_test() {
assumingThat("CI".equals(System.getenv("ENV")),
() -> assertEquals(2, new Study(2).getLimit()));
assertEquals(10, new Study(10).getLimit());
}
@Test
@EnabledOnJre(value = {JRE.JAVA_8, JRE.JAVA_11})
void test_on_java8_or_java11() {
assertNotNull(new Study(5));
}
}
@Enable__
, @Disable__
형태의 어노테이션이 제공되며 개발환경과 로컬환경, 환경변수, JAVA 버전 또는 OS 등의 조건을 명시하여 테스트를 작성할 수 있습니다.
이 때 명시된 조건에 부합하지 않으면 @Disable
처리되어 해당 테스트 메서드가 실행되지 않습니다.
태그와 필터링
public class TagTest {
@Test
@Tag("fast")
void fast_test() {
assertEquals("HELLO", new String("HELLO"));
}
@Test
void just_test() {
assertEquals("WORLD", new String("WORLD"));
}
}
테스트 클래스와 메서드는 @Tag
어노테이션을 통해 태그할 수 있습니다.
태그를 정의한 후 테스트 환경을 설정하여 특정 태그들만 필터링해서 테스트를 수행할 수 있도록 해줍니다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@Tag("fast")
public @interface Fast {
}
또 위와 같이 직접 작성한 커스텀 어노테이션을 사용할 수도 있습니다.
반복 테스트
@Slf4j
public class RepeatTest {
private static int count = 0;
@RepeatedTest(value = 10)
void repeatTest_1() {
log.info("count : {}", count++);
}
@DisplayName("반복 테스트")
@RepeatedTest(value = 10, name = "{displayName}, {currentRepetition}/{totalRepetitions}")
void repeatTest_2() {
log.info("count : {}", count++);
}
}
@RepeatedTest
어노테이션을 붙이면 해당 메서드를 설정한 값만큼 반복시킬 수 있습니다.
repetition n of value
의 형태로 테스트의 이름이 출력되며 name
설정으로 이를 변경할 수 있습니다.
@ParameterizedTest
@ValueSource(strings = {"Hello", "World", "Test", "JUnit5"})
void repeatTest_3(String message) {
log.info("message : {}", message);
}
@ParameterizedTest
어노테이션은 테스트 메서드에 인자를 넘겨서 수행할 수 있는 기능을 제공합니다.
테스트에 넘길 인자는 @ValueSource
로 정의할 수 있으며 기본 데이터 타입부터 객체까지 전달할 수 있습니다.
테스트 클래스의 라이프 사이클
반복 테스트 예제에서 count
변수를 static
으로 선언하지 않으면 어떻게 될까요?
0으로만 찍히는 것을 보면 테스트 메서드를 반복 수행할 때마다 새로운 테스트 인스턴스를 생성하는 것을 알 수 있습니다.
JUnit의 기본 전략은 테스트 메서드마다 테스트 인스턴스를 새로 생성하도록 설정되어 있습니다. 테스트 메서드를 독립적으로 실행해서 사이드 이펙트를 방지하기 위함이며 변경 가능합니다.
@TestInstance(Lifecycle.PER_CLASS)
어노테이션을 사용하면 같은 인스턴스 안에서 모든 테스트 메서드를 실행할 수 있습니다. 이 경우에는 인스턴스 변수를 모든 테스트 메서드가 공유하므로 @BeforeEach
또는 @AfterEach
로 상태를 초기화해줄 필요가 있습니다.
테스트 실행 순서
기본적으로 JUnit은 테스트 메서드 실행 순서를 보장하지 않습니다. 테스트는 언제든 독립적으로 실행되어 성공해야하기 때문인데 순서를 직접 정의할 수도 있습니다.
@TestMethodOrder
어노테이션으로 MethodOrderer
를 구현해서 사용하면 순서를 지정할 수 있습니다.
- DisplayName :
@DisplayName
기반 정렬 - MethodName : 메서드 이름 정렬
- OrderAnnotation :
@Order
순서 기반 정렬 - Random
의 구현체가 제공되며 커스텀해서 사용하는 방법도 존재합니다.
@Slf4j
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OrderTest {
int count = 0;
@Test
@Order(1)
void first_test() {
log.info("count : {}", count);
}
@Test
@Order(0)
void second_test() {
log.info("count : {}", count);
}
@Test
@Order(1)
void third_test() {
log.info("count : {}", count);
}
@Test
@Order(-5)
void fourth_test() {
log.info("count : {}", count);
}
}
@Order
어노테이션에 순서를 명시하면 오름차순으로 테스트 메서드를 실행하게 됩니다.
이 때 정의한 순서가 같다면 JUnit 내부의 순서에 의해 실행됩니다.
댓글남기기