JUnit 5 매개변수 테스트

내 이 세상 도처에서 쉴 곳을 찾아보았으나, 마침내 찾아낸, 컴퓨터가 있는 구석방보다 나은 곳은 없더라.

JUnit 5 매개변수 테스트

JUnit 4에서는 매개변수 테스트(parameterized test)를 작성할 때 불편한 점이 몇 가지 있다. 가장 대표적인 단점은 다음과 같이 정리할 수 있겠다.

  • 테스트 데이터 집합을 한 가지만 정의할 수 있다. 데이터 집합을 여러 개 정의한다고 컴파일 에러가 발생하지는 않지만 실제로 실행해보면 하나만 사용된다.
  • 매개변수 테스트 클래스에 일반 테스트가 포함되어 있으면 이 일반 테스트가 데이터 수만큼 반복 실행된다. IDE에서 테스트 결과를 보면 각 테이터 밑으로 일반 테스트 결과가 반복해 나타나 알아보기도 어렵다.
  • 매개변수 테스트 클래스는 @RunWith(ParameterizedTest.class) 어노테이션을 붙여야 하는데, 다른 @RunWith를 사용해야 할 경우 답답해진다.
  • 매개변수 데이터 집합 모양에 맞게 생성자를 정의해야 한다.

이런 이유로 테스트 대상 클래스에 매개변수 테스트를 하고 싶은 메서드가 여럿 있다면 매개변수 테스트 클래스 여럿을 만들어야 한다. 매개변수가 필요 없는 테스트가 있다면 따로 모아 별도 테스트 클래스에 추가해야 한다.

회전 문자열 검사 방법에서 구현했던 isRotated()에 대한 테스트를 JUnit 4로 작성하면 다음과 같다. 테스트 데이터 집합을 하나만 정의할 수 있으므로 모든 테스트 데이터와 기대 결과를 한 곳에 때려넣었다.

@RunWith(Parameterized.class)
public class StringRotationJunit4Test {
  private String input;
  private String rotated;
  private boolean result;

  public StringRotationJunit4Test(String s1, String s2, boolean result) {
    this.input = s1;
    this.rotated = s2;
    this.result = result;
  }

  @Parameters(name = "isRotated({0}, {1}) should be true {2}")
  public static Object[][] rotated() {
    return new Object[][]{
      {"abcde", "bcdea", true},
      {"abcde", "cdeab", true},
      {"abcda", "bcdaa", true},
      {"abbcd", "bbcda", true},
      {"", "", false},
      {"", "abc", false},
      {"abc", "", false},
      {null, "abc", false},
      {"abc", null, false},
      {"abc", "abc", false},
      {"abcde", "abcdef", false},
      {"abcda", "bcdaf", false}
    };
  }

  @Test
  public void isRotatedTrue() {
    assertEquals(result, StringRotation.isRotated(input, rotated));
  }
}

JUnit 5에서는 매개변수 테스트 데이터 정의 및 테스트 작성 방법이 달라졌다. 테스트 클래스에 별도 어노테이션을 붙이지 않는다. 대신 매개변수 테스트 메서드에 @ParameterizedTest 어노테이션을 붙인다. 따라서 한 테스트 클래스 안에 여러 매개변수 테스트를 정의할 수 있게 되었다.

또한 하나의 테스트 클래스 안에서 여러 테스트 데이터 집합을 정의할 수 있다. 데이터 집합을 정의하는 방법도 다음과 같이 다양하다.

  • @ValueSource
    테스트 데이터가 간단한 목록일 경우 사용할 수 있다. 값의 타입에 short, byte, int, long, float, double, char, String, Class만 사용할 수 있다는 제한이 있다.
  • @CvsSource
    매개변수 테스트에 여러 값을 넘겨야 하는 경우 사용할 수 있다. delimiter로 다른 문자를 지정할 수도 있다.
  • @CvsFileSource
    별도 CVS 파일을 테스트 데이터로 지정할 수도 있다. numLinesToSkip을 지정해 헤더를 건너뛰도록 할 수 있다.
  • @MethodSource
    매개변수 데이터를 제공할 메서드를 정의하고, 테스트 메서드에서 @MethodSource로 데이터 소스를 지정할 수 있다. 복잡한 객체를 테스트 인자로 넘겨야 하는 경우 유용하다.

문서를 참조하면 각 방법에 대한 자세한 사항을 알 수 있다.

위에서 JUnit 4로 작성했던 테스트를 JUnit 5로 다시 작성하면 다음과 같다. 두 가지 테스트 데이터 집합을 정의했고 이를 이용하는 두 개의 매개변수 테스트 메서드가 있다.

class StringRotationTest {
  private static Stream<Arguments> rotated() {
    return Stream.of(
      Arguments.of("abcde", "bcdea"),
      Arguments.of("abcde", "cdeab"),
      Arguments.of("abcda", "bcdaa"),
      Arguments.of("abbcd", "bbcda")
    );
  }

  private static Stream<Arguments> nonRotated() {
    return Stream.of(
      Arguments.of("", ""),
      Arguments.of("", "abc"),
      Arguments.of("abc", ""),
      Arguments.of(null, "abc"),
      Arguments.of("abc", null),
      Arguments.of("abc", "abc"),
      Arguments.of("abcde", "abcdef"),
      Arguments.of("abcda", "bcdaf")
    );
  }

  @ParameterizedTest
  @MethodSource("rotated")
  void isRotatedTrue(String input, String rotated) {
    assertTrue(StringRotation.isRotated(input, rotated));
  }

  @ParameterizedTest
  @MethodSource("nonRotated")
  void isRotatedFalse(String input, String rotated) {
    assertFalse(StringRotation.isRotated(input, rotated));
  }
}

물론 매개변수와 상관 없는 다른 테스트도 여기에 추가할 수도 있다. @ParameterizedTest가 아닌 @Test어노테이션이 붙은 메서드는 한 번만 실행된다. JUnit 4보다 매개변수 테스트를 작성하기가 한결 쉬워졌다.

참고