개발 공부 기록하기/01. JAVA & Kotlin

Junit5 Parameterized Test 가이드

lannstark 2020. 9. 2. 08:13

REF : https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests

Parameterized Test와 관련된 공식 document를 번역+요약+개인 의견을 첨부하며 실제 타이핑해본 것을 정리한 포스트입니다...!!

Parameterized Test란?

  • 여러 argument를 이용해 테스트를 여러번 돌릴 수 있는 테스트를 할 수 있는 기능
  • 사용하기 위해서는 @Test 대신 @ParameterizedTest 를 붙이면 된다.
  • @ParameterizedTest를 사용하게 되면 최소 하나의 source 어노테이션을 붙여주어야 한다. 예를 들어, 다음 테스트는 배열로 argument를 전달하는 @ValueSorce이다
@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
  // Palindrome은 회문이다 ㅎㅎ 토마토 기러기 처럼..!!  
  assertTrue(StringUtils.isPalindrome(candidate)); 
}

이 테스트를 수행하면 결과가 다음과 같이 나오게 된다. (총 3번 테스트가 돌아가는 것이다)

palindromes(String) ✔
├─ [1] candidate=racecar ✔
├─ [2] candidate=radar ✔
└─ [3] candidate=able was I ere I saw elba ✔

다양한 Source 종류들

Parameterized Test 사용법은 생각보다 간단하다. 이제 중요한 것은 파라미터에 들어갈 source를 어떻게 넣어줄 것인가로 생각된다. 이제 사용할 수 있는 source annotation들에 대해 살펴보자

ValueSource

  • argument가 하나인 테스트에 사용할 수 있다.
  • short, byte, int, long ,float, double, char, boolean, String, java.lang.Class에 사용할 수 있다.

사용자가 직접 만든 클래스는 사용할 수 없는 듯 하다

@ParameterizedTest
@ValueSource({new Car(1000, "현대차")}) // 여기서 에러가 난다
void valueSourceTest(car car) {

}

NullSource, EmptySource, NullAndEmptySource

  • NullSource : null을 보낸다
  • EmptySource : 비어 있는 배열, 컬렉션, 문자열 등을 반환한다
  • NullAndEmptySource : NullSource와 EmtpySource를 합쳐놓은 것이다
@ParameterizedTest
@NullAndEmptySource
void nullAndEmptySourceTest(List<String> list) {
  System.out.println(list); // null과 빈 리스트([])가 각각 출력된다
}

EnumSource

  • Enum을 argument로 보내줄 수 있다. (역시 argument가 하나이다 ㅠㅠ)

다음과 같은 enum이 있다고 하자!!

public enum Planet {
  EARTH,
  MARS,
  JUPITER;
}

이 Enum을 EnumSource로 활용할 수 있는 방법은 3가지 정도 존재한다

  1. Enum 전체를 가져오기
@ParameterizedTest
@EnumSource
void enumSourceTest(Planet planet) {

  // EARTH, MARS, JUPITER가 각각 들어오게 된다
  // Planet이라는 enum 구현체를 파라미터 타입에 명시했기 때문이다
  // @EnumSource(Planet.class) 라고 할 수도 있다

}
  1. Enum의 특정 목록만 가져오기
@ParameterizedTest
@EnumSource(names = {"MARS", "JUPITER"})
void enumSourceTest(Planet planet) {

  // MARS, JUPITER만 각각 들어오게 된다

}

@ParameterizedTest
@EnumSource(mode = EXCLUDE, names = {"MARS", "JUPITER"})
void enumSourceTest(Planet planet) {

  // EARTH만 들어온다

}
  1. regex 사용하기
@ParameterizedTest
@EnumSource(mode = MATCH_ALL, names = "^.*RS$")
void enumSourceTest(Planet planet) {

  // MARS만 들어온다

}

MethodSource

  • 테스트 클래스 내의 메소드 혹은 외부 클래스의 메소드가 반환하는 값을 source로 삼는 것이다.
  • 테스트 클래스 내에 있고 @TestInstance(Lifecycle.PRE_CLASS)를 붙인 것이 아니라면, 모두 static 메소드여야 한다.
  • 한 파라미터만 넣을 수도 있고, 여러 파라미터를 넣을 수도 있다.
  • 테스트 메소드와 이름이 동일하면 source를 명시적으로 적어주지 않아도 된다.
  1. 파라미터를 한 개만 넣으려면 Stream 을 반환하면 된다. IntStream, DoubleStream, LongStream등을 반환해도 괜찮다.
@ParameterizedTest
@MethodSource("parameterProvider")
void methodSourceTest(String argument) {

  // apple, banana가 각각 들어온다

}

static Stream<String> parameterProvider() {
  return Stream.of("apple", "banana");
}
  1. 파라미터를 여러개 넘기려면 Stream<Arguments> 를 반환해야 한다. Arguments 객체는 Arguments.of 혹은 arguments로 생성할 수 있다.
@ParameterizedTest
@MethodSource("parametersProvider")
void methodSourceTest(String str, int num, List<String> list) {

  // (apple, 1, [a, b])랑 (banana, 2, [x, y])

}

static Strema<Arguments> parametersProvider() {
  return Stream.of(
    arguments("apple", 1, Arrays.asList("a", "b")),
    arguments("banana", 2, Arrays.asList("x", "y"))
  );
}

CsvSource

  • CsvSource는 우리가 흔히 알고 있는 , 로 분리된 문자열을 테스트 메소드의 파라미터로 넣어준다
  • delimiterString 이라는 옵션으로 문자열을 분리할 다른 구분자를 고를 수 있다.
  • ' ' 로 넣는 것은 empty가 들어오고 아예 비워두는 것은 null이 들어오게 된다
@ParameterizedTest
@CsvSource({
  "apple,    1",
  "banana,   2",
  "'lemon, lime', 3"
})
void csvSourceTest(String fruti, int rank) {

  // (apple, 1) (banana, 2) ("lemon, lime", 3)이 각각 들어온다

}

이 외에도 CsvFileSource, ArgumentSource 등이 있으나 잘 사용될 것 같지는 않다.

자동 형변환

Argument로 문자열이 들어올때 특별한 형식에 맞추면 기본적으로 다른 Type의 객체로 바꿔준다..!! 예를 들어 "2020-01-01"이 들어오면 LocalDate로 들어올 수 있다.

자세한 형변환은 여기를 확인해보면 된다.

그리고 만약, 형변환 규칙이 적용되지 않았을 때는 public 정적 팩토리 메소드나 public 생성자에 주어진 파라미터 들을 넣어 보는 fallback 시나리오가 존재한다.

@ParameterizedTest
@ValueSource(strings = "42 Cats")
void testWithImplicitFallbackArgumentConversion(Book book) {
    assertEquals("42 Cats", book.getTitle());
}

public class Book {

    private final String title;

    private Book(String title) {
        this.title = title;
    }

    public static Book fromTitle(String title) {
        return new Book(title);
    }

    public String getTitle() {
        return this.title;
    }
}

예를 들어 위의 경우에는 "42 Cats" 문자열이 Book으로 바뀌는 기본 정책은 링크가 걸려 있는 목록에서 확인할 수 없지만, Book 객체의 public 정적 팩토리인 fromTitle 메소드가 자동 호출되어 Book 객체로 변환될 수 있다.

명시적으로 SimpleArgumentConverter를 상속한 ArgumentConverter를 만들 수도 있다. (많이 쓰일 것 같지는 않다)

Display Name 조절하기

@DisplayName("Display name of container")
@ParameterizedTest(name = "{index} ==> the rank of ''{0}'' is {1}")
@CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 3" })
void testWithCustomDisplayNames(String fruit, int rank) {

}

@ParameterizedTest 에 name 옵션을 주어, index, {0}, {1} 등등을 사용할 수 있다. index는 1부터 시작하는 반복횟수이고 {0}과 {1}은 첫 번째 argument, 두 번째 argument이다. 예를 들어 위의 테스트는 아래와 같이 출력된다.

Display name of container ✔
├─ 1 ==> the rank of 'apple' is 1 ✔
├─ 2 ==> the rank of 'banana' is 2 ✔
└─ 3 ==> the rank of 'lemon, lime' is 3 ✔

추가로 쓸 수 있는 템플릿은 아래와 같다.

  • {displanName} : method의 이름을 보여준다
  • {arguments} : comma로 구분된 파라미터를 모두 보여준다
  • {argumentsWithNames} : comma로 구분된 파라미터 이름과 파라미터를 모두 보여준다