1. 개요

의 JUnit 5 라이브러리 이벤트는 이전 버전에 비해 많은 새로운 기능을 제공합니다. 이러한 기능 중 하나는 테스트 템플릿 입니다. 간단히 말해서 테스트 템플릿은 JUnit 5의 매개변수화되고 반복되는 테스트를 강력하게 일반화한 것입니다 .

이 예제에서는 JUnit 5를 사용하여 테스트 템플릿을 만드는 방법을 배울 것입니다.

2. 메이븐 의존성

pom.xml에 의존성을 추가하는 것으로 시작하겠습니다 .

주요 JUnit 5 junit-jupiter-engine 의존성 을 추가해야 합니다  .

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.7.0</version>
</dependency>

이 외에도 junit-jupiter-api 의존성 을 추가해야 합니다 .

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.7.0</version>
</dependency>

마찬가지로 build.gradle 파일에 필요한 의존성을 추가할 수 있습니다 .

testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0'
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0'

3. 문제 설명

테스트 템플릿을 보기 전에 JUnit 5의 매개변수화된 테스트를 간단히 살펴보겠습니다 . 매개변수화된 테스트를 통해 다른 매개변수를 테스트 방법에 주입할 수 있습니다. 결과적으로 매개변수화된 테스트를 사용할 다른 매개변수를 사용하여 단일 테스트 메소드를 여러 번 실행할 수 있습니다.

이제 테스트 메서드를 여러 번 실행하고 싶다고 가정해 봅시다. 다른 매개변수뿐 아니라 매번 다른 호출 컨텍스트에서도 마찬가지입니다.

즉, 다음과 같은 다른 구성 조합을 사용하여 각 호출과 함께 테스트 메서드를 여러 번 실행하기를 원합니다 .

  • 다른 매개변수 사용
  • 테스트 클래스 인스턴스를 다르게 준비합니다. 즉, 테스트 인스턴스에 다른 의존성을 주입합니다.
  • 환경이 " QA "인 경우 호출의 하위 집합을 활성화/비활성화하는 것과 같은 다양한 조건에서 테스트 실행
  • 다른 수명 주기 콜백 동작으로 실행 — 아마도 우리는 호출의 하위 집합 전후에 데이터베이스를 설정하고 해제하기를 원할 것입니다.

이 경우 매개변수화된 테스트를 사용하는 것이 제한적임이 빠르게 증명됩니다. 고맙게도 JUnit 5는 이 시나리오에 대한 강력한 솔루션을 테스트 템플릿 형태로 제공합니다.

4. 테스트 템플릿

테스트 템플릿 자체는 테스트 케이스가 아닙니다. 대신 이름에서 알 수 있듯이 주어진 테스트 케이스에 대한 템플릿일 뿐입니다. 매개변수화되고 반복되는 테스트의 강력한 일반화입니다.

테스트 템플릿은 호출 컨텍스트 공급자가 제공한 각 호출 컨텍스트에 대해 한 번씩 호출됩니다.

이제 테스트 템플릿의 예를 살펴보겠습니다. 위에서 설정한 대로 주요 액터는 다음과 같습니다.

  • 테스트 대상 방법
  • 테스트 템플릿 방법
  • 템플릿 메소드에 등록된 하나 이상의 호출 컨텍스트 제공자
  • 각 호출 컨텍스트 제공자가 제공하는 하나 이상의 호출 컨텍스트

4.1. 테스트 대상 방법

이 예에서는 간단한 UserIdGeneratorImpl.generate 메서드를 테스트 대상으로 사용할 것입니다.

UserIdGeneratorImpl 클래스를 정의해 보겠습니다 .

public class UserIdGeneratorImpl implements UserIdGenerator {
    private boolean isFeatureEnabled;

    public UserIdGeneratorImpl(boolean isFeatureEnabled) {
        this.isFeatureEnabled = isFeatureEnabled;
    }

    public String generate(String firstName, String lastName) {
        String initialAndLastName = firstName.substring(0, 1).concat(lastName);
        return isFeatureEnabled ? "bael".concat(initialAndLastName) : initialAndLastName;
    }
}

생성 이 테스트 대상이되는 방법은, 상기 소요 인 firstNamelastName이라는 파라미터로하고 사용자 ID를 생성한다. 사용자 ID의 형식은 기능 전환의 활성화 여부에 따라 다릅니다.

이것이 어떻게 보이는지 봅시다:

Given feature switch is disabled When firstName = "John" and lastName = "Smith" Then "JSmith" is returned
Given feature switch is enabled When firstName = "John" and lastName = "Smith" Then "baelJSmith" is returned

다음으로 테스트 템플릿 메서드를 작성해 보겠습니다.

4.2. 테스트 템플릿 방법

다음은 테스트 대상 메서드인 UserIdGeneratorImpl.generate에 대한 테스트 템플릿입니다 .

public class UserIdGeneratorImplUnitTest {
    @TestTemplate
    @ExtendWith(UserIdGeneratorTestInvocationContextProvider.class)
    public void whenUserIdRequested_thenUserIdIsReturnedInCorrectFormat(UserIdGeneratorTestCase testCase) {
        UserIdGenerator userIdGenerator = new UserIdGeneratorImpl(testCase.isFeatureEnabled());

        String actualUserId = userIdGenerator.generate(testCase.getFirstName(), testCase.getLastName());

        assertThat(actualUserId).isEqualTo(testCase.getExpectedUserId());
    }
}

테스트 템플릿 방법을 자세히 살펴보겠습니다.

To begin with, we create our test template method by marking it with the JUnit 5 @TestTemplate annotation.

Following that, we register a context provider, UserIdGeneratorTestInvocationContextProvider, using the @ExtendWith annotation. We can register multiple context providers with the test template. However, for the purpose of this example, we register a single provider.

Also, the template method receives an instance of the UserIdGeneratorTestCase as a parameter. This is simply a wrapper class for the inputs and the expected result of the test case:

public class UserIdGeneratorTestCase {
    private boolean isFeatureEnabled;
    private String firstName;
    private String lastName;
    private String expectedUserId;

    // Standard setters and getters
}

Finally, we invoke the test target method and assert that that result is as expected

이제 호출 컨텍스트 공급자를 정의할 시간 입니다.

4.3. 호출 컨텍스트 제공자

테스트 템플릿에 하나 이상의 TestTemplateInvocationContextProvider 를 등록해야 합니다 . 등록된 각 TestTemplateInvocationContextProviderTestTemplateInvocationContext 인스턴스 스트림제공 합니다 .

이전에는 @ExtendWith 어노테이션을 사용하여 UserIdGeneratorTestInvocationContextProvider를 호출 공급자로 등록 했습니다 .

이제 이 클래스를 정의해 보겠습니다.

public class UserIdGeneratorTestInvocationContextProvider implements TestTemplateInvocationContextProvider {
    //...
}

호출 컨텍스트 는 두 가지 메서드가 있는 TestTemplateInvocationContextProvider 인터페이스를 구현합니다 .

  • 지원 테스트 템플릿
  • 제공하는 TestTemplateInvocationContexts

이행에 의한하자의 시작 supportsTestTemplate의 방법 :

@Override
public boolean supportsTestTemplate(ExtensionContext extensionContext) {
    return true;
}

JUnit 5 실행 엔진 은 제공자가 제공된 ExecutionContext에 적용 가능한지 확인하기 위해 먼저 supportTestTemplate 메소드를 호출합니다 . 이 경우 단순히 true 를 반환 합니다 .

이제 providerTestTemplateInvocationContexts 메소드를 구현해 보겠습니다 .

@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
  ExtensionContext extensionContext) {
    boolean featureDisabled = false;
    boolean featureEnabled = true;
 
    return Stream.of(
      featureDisabledContext(
        new UserIdGeneratorTestCase(
          "Given feature switch disabled When user name is John Smith Then generated userid is JSmith",
          featureDisabled,
          "John",
          "Smith",
          "JSmith")),
      featureEnabledContext(
        new UserIdGeneratorTestCase(
          "Given feature switch enabled When user name is John Smith Then generated userid is baelJSmith",
          featureEnabled,
          "John",
          "Smith",
          "baelJSmith"))
    );
}

의 목적 provideTestTemplateInvocationContexts의  방법은 제공하는 스트림TestTemplateInvocationContext의 인스턴스를. 이 예에서는 featureDisabledContextfeatureEnabledContext 메서드에서 제공하는 두 개의 인스턴스를 반환합니다 . 결과적으로 테스트 템플릿은 두 번 실행됩니다.

다음 으로 이러한 메서드에서 반환된 두 개의 TestTemplateInvocationContext 인스턴스를 살펴보겠습니다 .

4.4. 호출 컨텍스트 인스턴스

호출 컨텍스트는 TestTemplateInvocationContext 인터페이스의 구현이며 다음 메서드를 구현합니다.

  • getDisplayName – 테스트 표시 이름 제공
  • getAdditionalExtensions – 호출 컨텍스트에 대한 추가 확장을 반환합니다.

첫 번째 호출 컨텍스트 인스턴스를 반환 하는 featureDisabledContext 메서드를 정의해 보겠습니다 .

private TestTemplateInvocationContext featureDisabledContext(
  UserIdGeneratorTestCase userIdGeneratorTestCase) {
    return new TestTemplateInvocationContext() {
        @Override
        public String getDisplayName(int invocationIndex) {
            return userIdGeneratorTestCase.getDisplayName();
        }

        @Override
        public List<Extension> getAdditionalExtensions() {
            return asList(
              new GenericTypedParameterResolver(userIdGeneratorTestCase), 
              new BeforeTestExecutionCallback() {
                  @Override
                  public void beforeTestExecution(ExtensionContext extensionContext) {
                      System.out.println("BeforeTestExecutionCallback:Disabled context");
                  }
              }, 
              new AfterTestExecutionCallback() {
                  @Override
                  public void afterTestExecution(ExtensionContext extensionContext) {
                      System.out.println("AfterTestExecutionCallback:Disabled context");
                  }
              }
            );
        }
    };
}

첫째, featureDisabledContext 메서드 에서 반환된 호출 컨텍스트의 경우 등록 하는 확장 은 다음과 같습니다.

  • GenericTypedParameterResolver매개변수 해석기 확장
  • BeforeTestExecutionCallback – 테스트 실행 직전에 실행되는 수명 주기 콜백 확장
  • AfterTestExecutionCallback – 테스트 실행 직후에 실행되는 수명 주기 콜백 확장

그러나 featureEnabledContext 메서드 에서 반환된 두 번째 호출 컨텍스트 의 경우 다른 확장 집합을 등록해 보겠습니다( GenericTypedParameterResolver 유지 ).

private TestTemplateInvocationContext featureEnabledContext(
  UserIdGeneratorTestCase userIdGeneratorTestCase) {
    return new TestTemplateInvocationContext() {
        @Override
        public String getDisplayName(int invocationIndex) {
            return userIdGeneratorTestCase.getDisplayName();
        }
    
        @Override
        public List<Extension> getAdditionalExtensions() {
            return asList(
              new GenericTypedParameterResolver(userIdGeneratorTestCase), 
              new DisabledOnQAEnvironmentExtension(), 
              new BeforeEachCallback() {
                  @Override
                  public void beforeEach(ExtensionContext extensionContext) {
                      System.out.println("BeforeEachCallback:Enabled context");
                  }
              }, 
              new AfterEachCallback() {
                  @Override
                  public void afterEach(ExtensionContext extensionContext) {
                      System.out.println("AfterEachCallback:Enabled context");
                  }
              }
            );
        }
    };
}

두 번째 호출 컨텍스트의 경우 등록하는 확장은 다음과 같습니다.

  • GenericTypedParameterResolver – 매개변수 해석기 확장
  • DisabledOnQAEnvironmentExtension – 환경 속성( application.properties 파일 에서 로드 )이 " qa "인 경우 테스트를 비활성화하는 실행 조건
  • BeforeEachCallback – 각 테스트 메서드 실행 전에 실행되는 수명 주기 콜백 확장
  • AfterEachCallback – 각 테스트 메서드 실행 후에 실행되는 수명 주기 콜백 확장

위의 예에서 다음을 알 수 있습니다.

  • 동일한 테스트 메소드가 여러 호출 컨텍스트에서 실행됩니다.
  • 각 호출 컨텍스트는 다른 호출 컨텍스트의 확장과 수와 성격이 모두 다른 고유한 확장 세트를 사용합니다.

결과적으로 테스트 메서드는 매번 완전히 다른 호출 컨텍스트에서 여러 번 호출될 수 있습니다. 그리고 여러 컨텍스트 제공자를 등록함으로써 테스트를 실행할 호출 컨텍스트의 추가 계층을 더 많이 제공할 수 있습니다.

5. 결론

이 기사에서 우리는 JUnit 5의 테스트 템플릿이 어떻게 매개변수화되고 반복되는 테스트의 강력한 일반화인지 살펴보았습니다.

먼저 매개변수화된 테스트의 몇 가지 제한 사항을 살펴보았습니다. 다음으로 테스트 템플릿이 각 호출에 대해 다른 컨텍스트에서 테스트를 실행할 수 있도록 하여 제한 사항을 극복하는 방법에 대해 논의했습니다.

마지막으로 새 테스트 템플릿을 만드는 예를 살펴보았습니다. 템플릿이 호출 컨텍스트 공급자 및 호출 컨텍스트와 함께 작동하는 방식을 이해하기 위해 예제를 세분화했습니다.

항상 그렇듯이 이 기사에 사용된 예제의 소스 코드는 GitHub에서 사용할 수 있습니다 .

Junit footer banner