JUnit5
- 자바 개발자가 가장 많이 사용하는 테스팅 프레임워크
- Java 8이상을 필요로 함.
- SpringBoot 2.2 버전이상부터 기본 테스트로 5버전이 채택
Platform : 테스트를 실행해주는 런처 제공. TestEngine API제공
Jupiter : TestEngin API 구현체로 JUnit5를 제공
Vintage : JUnit 4와 3를 지원하는 TestEngine 구현체
스프링부트를 사용할 경우 spring-boot-starter-test 에 기본적으로 탑제된다.
사용하지 않을경우 dependency를 추가해주면 된다.
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
기본 어노테이션
- @Test : 기본적으로 Test를 진행하고자 하는 Method에 적용
- @BeaforAll : 테스트를 실행하기전에 딱 1번 실행하는 Method는 static 이어야 하며, private불가, default 가능, return type이 있으면 안됨. 즉, static void로만 사용
- @AfterAll : BeforAll의 반대로 테스트가 종료된 후 한번실행
- @BeforeEach, @AfterEach : 모든 테스트 이전 / 이후에 실행
- @Disabled : TestAnnotation이 붙어있는 경우 테스트를 disable시키는 annotation
- 그 외 어노테이션은 가이드문서 2-1 참고
테스트 이름 표기하기
메서드 이름으로 어떤 테스트인지 표현하려면 길어질 수 있으니 DisplayName을 사용하여 표기할 수 있다.
DisplayNameGeneration
- Method와 Class레퍼런스를 사용하여 테스트 이름을 표기하는 방법 설정
- 기본 구현체로 ReplaceUnderscores제공
DisplayName
- 어떤 테스트인지 테스트 이름을 보다 쉽게 표현할 수 있는 방법을 제공하는 어노테이션
- @DisplayNameGeneration보다 우선순위가 높다.
그 외 방법은 가이드문서 2-3 참고
Assertion
테스트 코드 작성시 기본적으로 static import 되어있음.
- assertEquals(expected, actual) : 실제 값이 기대한 값과 같은지 확인
- assertNotnull(actual) : 값이 null이 아닌지 확인
- assertTure(boolean) : 다음 조건이 참(true)인지 확인
- assertAl(excutables…) : 모든 확인 구문 확인
- assertThrows(expectedType, executable) : 예외 발생 확인
- assertTimeout(duration, executable) : 특정시간 안에 실행이 완료되는지 확인
assertion은 마지막 매개변수로 Supplier
복잡한 메시지를 생성하거나, 연산이필요한 경우 메시지를 직접작성하는것보다 자원에 효율적임.
AssertJ, Hemcrest, Truth등의 라이브러리를 사용할 수 있다.
조건에 따라 테스트 실행하기
org.junit.jupiter.api.Assumptions.*
- assumeTrue(조건)
- assumingTha
- EnabledOnOs({OS.MAC, OS.LINUX})
- EnabledOnJre({JRE.JAVA_8, …})
- EnabledIfEnvironmentVariable(환경변수, matchs)
태깅과 필터링
태깅 → 테스트그룹을 만들고 원하는 테스트그룹만 실행하는
@Tag를 사용하여 group을 지정할 수 있다.
Intellij edit configuration / maven
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<groups>fast | slow</groups>
</configuration>
</plugin>
커스텀 태그
JUnit 5 애노테이션을 조합하여 커스텀 태그를 만들 수 있다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@Test
public @interface FastTest {
}
@FastTest
@DisplayName("스터디 만들기 fast")
void create_new_study() {
@SlowTest
@DisplayName("스터디 만들기 slow")
void create_new_study_again() {
테스트 반복하기
반복횟수와 반복 테스트 이름을 설정할 수 있다.
@RepeatedTest(value=횟수, name="{displayName},{currentRepetition}/{totalRepetitions})
void repeatTest(RepetitionInfo repetitionInfo){} // 반복횟수
@ParameterizedTest
테스트에 여러 다른 매개변수를 대입해가며 반복 실행한다.
- {displayName}
- {index}
- {arguments}
- {0}, {1}, …
@DisplayName("테스트")
@ParameterizedTest(name = "{index} {displayname} message={0}")
@ValueSource(strings = {"message1", "message2"})
void parameterizedTest(String message) {}
//테스트이름 -> 1 테스트 message=message1
//테스트이름 -> 2 테스트 message=message2
인자값으로 넘겨줄 수 있는
- @ValueSource
- @NullSource, @EmptySource, @NullAndEmptySource
- @EnumSource
- @MethodSource
- @CvsSource
- @CvsFileSource
- @ArgumentSource
인자 값 타입 변환
- 암묵적인 타입 변환
- 레퍼런스 참고
- 명시적인 타입 변환
- SimpleArgumentConverter 상속 받은 구현체 제공
-
@ParameterizedTest @EnumSource(ChronoUnit.class) void testWithExplicitArgumentConversion( @ConvertWith(ToStringArgumentConverter.class) String argument) { assertNotNull(ChronoUnit.valueOf(argument)); } public class ToStringArgumentConverter extends SimpleArgumentConverter { @Override protected Object convert(Object source, Class<?> targetType) { assertEquals(String.class, targetType, "Can only convert to String"); if (source instanceof Enum<?>) { return ((Enum<?>) source).name(); } return String.valueOf(source); } }
인자 값 조합
- ArgumentsAccessor
- 커스텀 Accessor
- ArgumentsAggregator 인터페이스 구현
- @AggregateWith
@ParameterizedTest @CsvSource({ "Jane, Doe, F, 1990-05-20", "John, Doe, M, 1990-10-22" }) void testWithArgumentsAccessor(ArgumentsAccessor arguments) { Person person = new Person(arguments.getString(0), arguments.getString(1), arguments.get(2, Gender.class), arguments.get(3, LocalDate.class)); if (person.getFirstName().equals("Jane")) { assertEquals(Gender.F, person.getGender()); } else { assertEquals(Gender.M, person.getGender()); } assertEquals("Doe", person.getLastName()); assertEquals(1990, person.getDateOfBirth().getYear()); }@ParameterizedTest @CsvSource({ "Jane, Doe, F, 1990-05-20", "John, Doe, M, 1990-10-22" }) void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) { // perform assertions against person } public class PersonAggregator implements ArgumentsAggregator { @Override public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) { return new Person(arguments.getString(0), arguments.getString(1), arguments.get(2, Gender.class), arguments.get(3, LocalDate.class)); } }
테스트 인스턴스
JUnit은 테스트 메소드 마다 테스트 인스턴스를 새로 만든다.
- 기본 전략
- 테스트 메소드를 독립적으로 실행하여 예상치 못한 부작용을 방지하기 위함이다.
- 이 전략을 JUnit5 에서 변경할 수 있다.
@TestInstance(Lifecycle.PER_CLASS)
- 테스트 클래스당 인스턴스를 하나만 만들어 사용한다.
- 경우에 따라, 테스트 간에 공유하는 모든 상태를 @BeforeEach 또는 @AfterEach에서 초기화 할 필요가 있다.
- @BeforeAll과 @AfterAll을 인스턴스 메소드 또는 인터페이스에 정의한 default 메소드로 정의할 수도 있다.
테스트 순서
실행할 테스트 메소드 특정한 순서에 의해 실행되지만 어떻게 그 순서를 정하는지는 의도적으로 분명히 하지 않는다. (테스트 인스턴스를 테스트 마다 새로 만드는 것과 같은 이유)
경우에 따라, 특정 순서대로 테스트를 실행하고 싶을 때도 있다. 그 경우에는 테스트 메소드를 원하는 순서에 따라 실행하도록 @TestInstance(Lifecycle.PER_CLASS)와 함께 @TestMethodOrder를 사용할 수 있다.
- MethodOrderer 구현체를 설정한다.
- 기본 구현체
- @Alphanumeric (6버젼에 remove될 예정)
- @Order
- @Random
junit-platform.properties
JUnit 설정 파일로, 클래스패스 루트 (src/test/resources/)에 넣어두면 적용
-
테스트 인스턴스 라이프사이클 설정
junit.jupiter.testinstance.lifecycle.default = per_class
-
확장팩 자동 감지 기능
junit.jupiter.extensions.autodetection.enabled = true
-
@Disabled 무시하고 실행하기
junit.jupiter.conditions.deactivate = org.junit.*DisabledCondition
-
테스트 이름 표기 전략 설정
junit.jupiter.displayname.generator.default = \
org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores
-
테스트 순서
junit.jupiter.testmethod.order.default =
org.junit.jupiter.api.MethodOrderer$OrderAnnotation
확장모델
JUnit 4의 확장 모델은 @RunWith(Runner), TestRule, MethodRule.
JUnit 5의 확장 모델은 단 하나, Extension.
확장팩 등록 방법
- 선언적인 등록 @ExtendWith
- 프로그래밍 등록 @RegisterExtension
- 자동 등록 자바 ServiceLoader 이용
확장팩 만드는 방법
- 테스트 실행 조건
- 테스트 인스턴스 팩토리
- 테스트 인스턴스 후-처리기
- 테스트 매개변수 리졸버
- 테스트 라이프사이클 콜백
- 예외 처리
EX)
@ExtendWith(TimingExtension.class)
public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
private static final Logger logger = Logger.getLogger(TimingExtension.class.getName());
private static final String START_TIME = "start time";
@Override
public void beforeTestExecution(ExtensionContext context) throws Exception {
getStore(context).put(START_TIME, System.currentTimeMillis());
}
@Override
public void afterTestExecution(ExtensionContext context) throws Exception {
Method testMethod = context.getRequiredTestMethod();
long startTime = getStore(context).remove(START_TIME, long.class);
long duration = System.currentTimeMillis() - startTime;
logger.info(() ->
String.format("Method [%s] took %s ms.", testMethod.getName(), duration));
}
private Store getStore(ExtensionContext context) {
return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod()));
}
}
JUnit 4 마이그레이션
junit-vintage-engine을 의존성으로 추가하면, JUnit 5의 junit-platform으로 JUnit 3과 4로 작성된 테스트를 실행할 수 있다.
-
@Rule은 기본적으로 지원하지 않지만, junit-jupiter-migrationsupport 모듈이 제공하는 @EnableRuleMigrationSupport를 사용하면 다음 타입의 Rule을 지원한다.
- ExternalResource
- Verifier
- ExpectedException
| JUnit 4 | JUnit 5 |
|---|---|
| @Category(Class) | @Tag(String) |
| @RunWith, @Rule, @ClassRule | @ExtendWith, @RegisterExtension |
| @Ignore | @Disabled |
| @Before, @After, @BeforeClass, @AfterClass | @BeforeEach, @AfterEach, @BeforeAll, @AfterAll |