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 {}

그리고 다음과 같이 BeforeTestExecutionCallbackAfterTestExecutionCallback 을 구현받는 클래스를 만듭니다. (테스트 실행 전 호출될 콜백과 테스트 실행 후 호출될 콜백을 정의합니다.)

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로 작성된 테스트를 실행할 수 있습니다.