[JUnit] JUnit 5
1. JUnit 5 테스트 인스턴스
테스트 클래스에 있는 각각의 테스트 메서드는 서로 다른 인스턴스에서 실행됩니다.
다음 코드를 살펴보겠습니다.
class MyTest {
int value = 0;
@Test
void first() { System.out.println(value++); }
@Test
void second() { System.out.println(value++); }
}
위 MyTest 클래스를 전체 테스트해보면 second()에서는 1이 찍혀야할거 같지만, 콘솔에 모두 0이 찍힙니다.
테스트 메서드는 서로 독립적이여야 한다는 특징때문에 각 테스트 메서드를 실행하는 인스턴스를 할당하는 것 같습니다.
만약, 하나의 인스턴스에서 실행되도록 만들고 싶다면 @TestInstance
를 사용하면 됩니다.
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTest { ... }
2. JUnit 5 테스트 순서
테스트 클래스에 있는 각각의 테스트들은 기본적으로 테스트마다 스레드로 서로 다른 인스턴스들이 실행하도록 하기 때문에 순서가 보장되지 않습니다. 그런데, use-case 테스트와 같이 순서가 보장되어야 하는 경우도 있습니다. 예를 들어, 회원 가입 -> 로그인 -> 조회 등 일련의 절차가 필요할 수 있습니다. 이때 @Order
애너테이션을 각 테스트마다 할당해 우선순위를 부여할 수 있습니다. (주의할 점은 몇 번째에 실행하겠다는게 아니라 우선순위입니다.) 그리고 각각의 테스트에 @Order
을 적용하려면 테스트 클래스에 @TestMethodOrder
를 명시해야 합니다.
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MyTest {
@Order(1) // 순서가 낮을수록 더 높은 우선순위를 가집니다.
@Test void first() {...}
@Order(2)
@Test void second() {...}
}
3. JUnit 5 확장 모델(Extension)
Junit 4의 확장 모델은 @RunWith(Runner), TestRule, MethodRule 이었는데,
Junit 5의 확장 모델은 단 하나, Extension 으로 통일되었습니다.
확장팩(Extension) 등록 방법
- 선언적인 등록
@ExtendWith
- 프로그래밍 등록
@RegisterExtension
- 자동 등록 자바
ServiceLoader
이용
확장팩 만드는 방법
- 테스트 실행 조건
- 테스트 인스턴스 팩토리
- 테스트 인스턴스 후-처리기
- 테스트 매개변수 리졸버
- 테스트 라이프사이클 콜백
- 예외 처리
- ...
3.1 시나리오
@SlowTest 애너테이션이 붙지 않은 테스트 메서드 중 실행 시간이 1005mills 이상이 걸리는 테스트에 한해 @SlowTest 적용을 고려하라는 문자열을 출력하고 싶다고 해보겠습니다. 우선 @SlowTest 애너테이션을 다음과 같이 작성합니다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Test
@Tag("slow")
public @interface SlowTest {}
그리고 다음과 같이 BeforeTestExecutionCallback
과 AfterTestExecutionCallback
을 구현받는 클래스를 만듭니다. (테스트 실행 전 호출될 콜백과 테스트 실행 후 호출될 콜백을 정의합니다.)
public class FindSlowTestExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
private static final long THRESHOLD = 1000L;
@Override
public void beforeTestExecution(ExtensionContext context) throws Exception {
String testClassName = context.getRequiredTestClass().getName();
String testMethodName = context.getRequiredTestMethod().getName();
ExtensionContext.Store store =
context.getStore(ExtensionContext.Namespace.create(testClassName, testMethodName));
store.put("START_TIME", System.currentTimeMillis());
}
@Override
public void afterTestExecution(ExtensionContext context) throws Exception {
String testClassName = context.getRequiredTestClass().getName();
String testMethod = context.getRequiredTestMethod();
String testMethodName = testMethod.getName();
ExtensionContext.Store store =
context.getStore(ExtensionContext.Namespace.create(testClassName, testMethodName));
Long startTime = store.remove("START_TIME", Long.class);
long duration = System.currentMillis() - startTime;
SlowTest annotation = testMethod.getAnnotation(SlowTest.class);
if (duration > THRESHOLD && annotation == null) {
System.out.printf("Please consider mark method [%s] with @SlowTest. \n", testMethodName);
}
}
}
위에서 만든 Extension을 적용하는 방법은 처음에 얘기했듯 3가지입니다.
첫 째, 선언적인 방법으로 테스트 클래스에 @ExtendWith
애너테이션을 적용합니다.
@ExtendWith(FindSlowTestExtension.class)
class MyTest { ... }
둘 째, 프로그래밍적인 방법은 위 방법보다 조금 더 유연한 방법입니다. (테스트 클래스마다 THRESHOLD 값을 설정할 수 있게)
class MyTest {
@RegisterExtension
static FindSlowTestExtension findSlowTestExtension =
new FindSlowTestExtension(1000L); // 물론 해당 생성자를 만들어야 합니다.
}
4. JUnit 4 -> JUnit 5로 마이그레이션하는 방법
Junit-vintage-engine을 의존성으로 추가하면, JUnit 5의 junit-platform으로 JUnit 3과 4로 작성된 테스트를 실행할 수 있습니다.