오늘은 junit5의 기본적인 사용법만 살펴보자. 예전에 릴리즈 되기 전에 여기에 대충 사용법만 포스팅한적이 있었다. 아주 junit5 의 기본적인 내용만 살펴봤으니 좀 더 많은 내용은 문서를 통해서 확인하면 더 좋을 듯 싶다. 많이 바뀐 내용은 없는 듯 하니 추가할 내용은 없을 것 같다. 기본적으로 class가 public이 아니여도 되고, test 메서드도 public이 아니고 package private 이여도 된다는 것은 동일하다. 아주아주 기본적인 사용법은 예전에 살펴본 내용이므로 생략하자. 그렇게 어려운 내용은 아니니 한번씩 해보면 좋을 것 같다.

ExtendWith

Junit5 에 추가된 어노테이션중에 하나이다. 어노테이션명 그대로 ExtendWith은 뭔가를 확장 시킬 수 있는 그런 어노테이션이다. 실제로 코드는 아래와 같다.

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Repeatable(Extensions.class)
@API(status = STABLE, since = "5.0")
public @interface ExtendWith {

    Class<? extends Extension>[] value();

}

별 다른 내용은 없고 value에는 Extension를 상속한 클래스만 가능하다. 실제로 Extension 인터페이스는 딱히 구현할 것도 없는 마커 인터페이스 이다. Extension를 사용하는 인터페이스도 많으니 한번씩 살펴보도록 하고 오늘할 내용은 ParameterResolver라는 인터페이스를 사용하는법을 알아보도록 하자. ParameterResolver 인터페이스는 파라미터를 컨트롤 할 수 있는 그런 인터페이스이다. 실제로 junit4에서는 무조건 기본생성자가 있어야 하며 test 메서드는 파라미터가 없어야 했었다. 하지만 junit5 부터는 기본생성자뿐만 아니라 test 메서드에도 파라미터가 있어도 된다. 하지만 그 타입에 맞게 구현은 해줘야 한다. 그게 바로 ParameterResolver 인터페이스이다.

public interface ParameterResolver extends Extension {

    boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
            throws ParameterResolutionException;

    Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
            throws ParameterResolutionException;
}

ParameterResolver 인터페이스는 다음과 같은 형태이다. supportsParameter 지원가능한 파라미터 타입을 검사하고 resolveParameter메서드는 실제 지원가능한 타입을 조작하는 그런 메서드라고 생각하면 된다. 뭔가 spring과 비슷하다. 약간 비슷할 수 밖에 없는게 junit5 를 개발한 개발자가 spring 팀에도 속해있기 때문이다. 그래서 비슷한 구석이 있을 수 있다.

한번 아주간단하게 구현을 해보자.

class UserInfoParameterResolver implements ParameterResolver {

  @Override
  public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
    return (parameterContext.getParameter().getType() == UserInfo.class);
  }

  @Override
  public UserInfo resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
    return new UserInfo("wonwoo", "wonwoo@test.com");
  }
}

ParameterResolver를 구현한 구현체이다. 간단하게 어떤 동작을 하는지 보는 것이기 때문에 의미 있는 코드는 아니니 참고하기 바란다.

class UserInfo {
  private final String name;
  private final String email;

  UserInfo(String name, String email) {
    this.name = name;
    this.email = email;
  }

  public String getEmail() {
    return email;
  }

  public String getName() {
    return name;
  }
}

다음은 UserInfo를 담는 Object이다. 간단하므로 자세한 내용은 생략한다. 어떻게 사용하는지 한번 살펴보자.

@ExtendWith(UserInfoParameterResolver.class)
class NestedUserInfoTest {

  private final UserInfo userInfo;

  NestedUserInfoTest(UserInfo userInfo) {
    this.userInfo = userInfo;
  }

  @Test
  void user_info_test() {
    assertEquals(userInfo.getName(), "wonwoo");
    assertEquals(userInfo.getEmail(), "wonwoo@test.com");
  }
}

위와 같이 기본 생성자가 없어도 테스트는 통과 한다. 아래와 같이 test 메서드에 파라미터로 UserInfo 타입을 받아도 동일한 결과를 얻을 수 있다.

@ExtendWith(UserInfoParameterResolver.class)
class UserInfoTest {

  @Test
  void user_info_test(UserInfo userInfo) {
    assertEquals(userInfo.getName(), "wonwoo");
    assertEquals(userInfo.getEmail(), "wonwoo@test.com");
  }
}

또한 spring과 비슷하다고 느낀점이 메타 어노테이션도 지원한다. 아래와 같이 특정한 어노테이션을 만든 후에 ExtendWith를 사용해도 된다.

@ExtendWith(UserInfoParameterResolver.class)
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface UserInfoExtension {
}

위와 같이 메타 어노테이션을 만들었다면 다음과 같이도 테스트를 할 수 있다.

@UserInfoExtension
class UserInfoTest {

  @Test
  void user_info_test(UserInfo userInfo) {
    assertEquals(userInfo.getName(), "wonwoo");
    assertEquals(userInfo.getEmail(), "wonwoo@test.com");
  }
}

아주 심플하게 테스트를 할 수 있어서 좋은 것 같다. 오늘은 이정도면 기본적인 개념도 좀 더 알 듯 싶고 다음시간에는 좀 더 활용할 수 있도록 Spring 혹은 mockito를 이용해서 테스트를 할 수 있도록 해보자.

물론 전체 소스도 여기에 있다. 좀 더 많은 소스가 있으니 차근차근 살펴봐도 될 듯 싶다.