[JUnit5] Extension
JAVA/JUnit

[JUnit5] Extension

테스트를 진행하면서, 추가적인 기능을 부여해야 하는 경우가 종종 있습니다. 이런 기능을 하는 모델들을 확장 모델(Extension Model)이라고 합니다. JUnit4에서 사용했던 Runner, TestRule, MethodRule들이 JUnit5가 되면서 Extension API로 통일되었습니다.

 

 

Extension 생성 - Lifecycle

사용하려는 케이스에 따라 Extension의 생성 방식 달라집니다. 자세한 내용은 SpringExtension.class를 살펴보시면 됩니다.

 

먼저 생명 주기를 기준으로 나누어보겠습니다.

생명 주기에 따라 사용되는 Extension이 다릅니다. 크게 범위를 보자면 다음과 같습니다.

실행 순서대로 설명하겠습니다.

 

1st. TestInstanceFactory

테스트 클래스 인스턴스를 생성하는 경우에 사용하는 Extension입니다.

주로 의존성 주입에서 테스트 인스턴스를 획득하거나, 테스트 클래스 인스턴스를 생성하기 위해 정적 팩토리 메서드를 호출하는 데 사용합니다.

 

이를 사용하는 대신에, Parameter Resolver Extension을 통해 해결할 수 있습니다.

 

TestInstanceFactory는 상속이 되기 때문에 실수로 중복된 구현체를 등록하지 않도록 주의해야 하며, 단일 클래스에 TestInstaceFactory를 구현하는 여러 Extension을 등록하면, 예외가 발생하고, 단 하나의 Extension만 등록해야 합니다.

 

 

2nd. Test Instance Post-Processing (TestInstancePostProcessor)

테스트 인스턴스가 생성된 후에 실행되는 Extension입니다.

주로 의존성 주입이나, 커스텀화 된 초기화 메서드 호출을 할 때 사용합니다.

 

 

3rd. Lifecycle Callbacks

본격적으로 메서드의 LifecycleCallbacks들입니다. 크게 세 범위로 나뉘어 있습니다.

  • 가장 큰 범위 : BeforeAllCallback, AfterAllCallback
  • 중간 범위 : BeforeEachCallback, AfterEachCallback
  • 작은 범위 : BeforeTestExecutionCallback, AfterTestExecutionCallback  - 테스트의 시간을 재거나, 추적할 때 사용

 

4th. TestInstancePreDestroyCallback

테스트가 사용된 후 소멸되기 직전에 실행되는 Extension입니다.

주로 의존성 정리, 초기화 해제 메서드 호출을 할 때 사용합니다.

 

 

주의사항

@TestInstance에 따라 Extension 실행 범위와 순서를 주의해야 합니다.

  • @TestInstance(TestInstance.Lifecycle.PER_CLASS)인 경우 인스턴스가 단 한 번만 생성되기 때문에, postProcess와 preDestroyCallback가 가장 먼저, 가장 마지막에 단 한번만 실행됩니다.
  • @TestInstance(TestInstance.Lifecycle.PER_METHOD)인 경우 인스턴스가 테스트마다 생성되기 때문에 BeforeAllCallback, AfterAllCallback가 가장 먼저 실행, 이후 각 테스트마다 postProcess, preDestroyCallback가 실행됩니다.

 

Extension 생성 - 이외의 생성

Conditional Test Execution (ExcutionCondition)

테스트 실행 여부를 제어하는 Extension입니다. ExcutionCondition 인터페이스를 구현해 정의합니다.

다수의 ExcutionCondition이 등록될 때, 그중 하나라도 비활성화가 된다면, 테스트는 비활성화됩니다.

그렇기 때문에 어느 조건 때문에 테스트가 비활성화되었는지 알기가 어렵습니다.

 

parameter resolution(ParameterResolver)

런타임 시 동적으로 파라미터를 선택하기 위한 Extension입니다.

생성자, 메서드, @BeforeAll과 같은 Lifecycle 메서드에 파라미터를 선언하게 되면, 그 파라미터는 ParameterResolver에 의해 파라미터가 반드시 선택됩니다. 

내장된 ParameterResolver 이외에도 커스텀 생성이 가능합니다.

(CustomTypeParameterResolver, TypeBasedParameterResolver 참고)

 

exception handling

테스트 실행 도중 예외가 발생한다면, 테스트 전체가 멈추기 때문에, 이 예외를 가로채어 처리를 할 필요가 있습니다.

TestExecutionExceptionHandler는 테스트 메서드 실행 중 발생한 예외를 처리하고, LifecycleMethodExecutionExceptionHandler는 LifeCycle Method에서 발생한 예외를 처리합니다.

특히 에러 로깅, 리소스 해제와 같은 역할을 수행하는 데 사용됩니다.

 

 

Extension 등록

이제 만든 Extension을 등록해봅시다! 등록하는 방법은 총 세 가지입니다.

1. @ExtendWith을 통해 선언적으로 등록

테스트 인터페이스 · 클래스 · 메서드 또는 커스텀 어노테이션에 @ExtendWith(...)을 사용해 하나 이상의 Extension을 선언적으로 등록이 가능합니다.

 

※ Jupiter 5.8 이후부터 생성자나, @BeforeAll과도 같은 라이프사이클 메서드의 필드 또는 파라미터에도 선언이 가능합니다.

 

@ExtendWith(Extension구현 클래스.class, ...)
인터페이스, 클래스, 메서드, 어노테이션 {
	...
}
  • @ExtendWith의 사용 위치는 라이프사이클 등을 고려해서 선언하시면 됩니다.
  • 확장에도 순서가 있습니다. 명시적으로 순서를 등록해야 하는 경우 @Order을 사용합시다.

 

 

2. @RegisterExtension을 통해 프로그래밍 방식으로 등록

Extension의 기준이 각 테스트 클래스마다 바뀌어야 한다고 생각해봅시다.

보통 생성자의 파라미터, 팩토리 메서드, 빌더 API를 통해 이를 조율하게 되는데, @ExtendWith의 경우에는 이와 같은 방법을 사용할 수 없습니다.

그래서 필드 레벨에서 직접 Extension 인스턴스를 커스터마이징 하는 방법이 필요합니다. @RegisterExtension가 바로 이러한 기능을 하는 어노테이션입니다.

// 생성자 형식
@RegisterExtension
static WebServerExtension webTestExtension = new WebServerExtension(false);

// 빌더 형식
@RegisterExtension
static WebServerExtension server = WebServerExtension.builder()
                    .enableSecurity(false)
                    .build();
  • 필드는 항상 null이어선 안되고 static, non-static은 모두 가능합니다.
    • static인 경우
      • @ExtendWith을 통해 클래스 수준에서 Extension 등록이 끝난 이후에 등록.
      • 클래스 생명주기 콜백 [BeforeAllCallback, TestInstance(PostProcessor, PreDerstroy)]와 같은 수준부터
        BeforeEachCallback와 같은 메서드 생명주기 수준까지 구현이 가능합니다.
    • non-static인 경우
      • @ExtendWith을 통해 메서드 수준에서 Extension 등록이 끝난 이후에 등록.
      • BeforeEachCallback와 같은 메서드 생명주기 수준부터 구현이 가능합니다.
      • 하지만, @TestInstance(Lifecycle.PER_CLASS)이 등록된 경우에는 static과 같은 수준에서 등록

 

 

3. Java의 ServiceLoader 메커니즘을 통해 자동으로 등록

프로퍼티 (junit-platform.properties)를 통해 전역 확장 등록을 하는 방법입니다. 잘 사용하는 방법은 아닙니다.

junit.jupiter.extensions.autodetection.enabled = true