Parameterized Tests 사용법

참고문서 – Guide to JUnit 5 Parameterized Tests

개요

Parameterized tests는 단일 테스트 메서드를 다양한 매개변수로 여러 번 실행할 수 있게 해준다. 이를 통해 반복적인 테스트 코드를 줄이고, 다양한 입력값에 대한 테스트를 간편하게 수행할 수 있다.

기본 구조

@ParameterizedTest
@ValueSource(ints = {1, 3, 5, -3, 15, Integer.MAX_VALUE}) // 여섯 개의 숫자
void isOdd_ShouldReturnTrueForOddNumbers(int number) {
    assertTrue(Numbers.isOdd(number));
}
  • 해당 테스트는 메서드를 6번 실행한다. @ValueSource 배열에서 다른 값을 number 매개변수에 할당한다.
    • @ParameterizedTest 어노테이션을 사용한다.
    • @ValueSource를 통해 매개변수 소스를 정의한다.

매개변수 소스

Simple Values

ValueSource: 리터럴 값의 배열을 전달한다.

  • 테스트 메서드에 한 번에 하나의 인수만 전달할 수 있다.
  • null값을 전달할 수 없다.
  • 지원하는 유형: short, byte, int, long, float, double, char, String, Class
@ParameterizedTest
    @ValueSource(strings = {"", "  "})
    void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input) {
        assertTrue(Strings.isBlank(input));
    }

NullSource & EmptySource: 단일 null 값 또는 빈 값을 전달한다.

  • Collection 유형과 배열에 대해 빈 값을 제공할 수 있다.
  • @NullAndEmptySource를 이용해 null 값과 빈 값을 모두 전달할 수 있다.
  • @ValueSource, @NullSource, @EmptySource를 결합하여 사용할 수 있다.
@ParameterizedTest
    @NullAndEmptySource
    @ValueSource(strings = {"  ", "\t", "\n"})
    void isBlank_ShouldReturnTrueForAllTypesOfBlankStrings(String input) {
        assertTrue(Strings.isBlank(input));
    }

EnumSource: 정의된 상수들을 전달한다.

@ParameterizedTest
@EnumSource(value = Month.class, names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"})
void someMonths_Are30DaysLong(Month month) {
    final boolean isALeapYear = false;
    assertEquals(30, month.length(isALeapYear));
}

CsvSource: CSV (Comma Separated Value) 형식의 데이터를 전달한다.

  • 기본적으로 쉼표 구분자를 사용하지만, delimiter 속성을 통해 이를 사용자 정의할 수 있다.
@ParameterizedTest
    @CsvSource({"test,TEST", "tEst,TEST", "Java,JAVA"})
    void toUpperCase_ShouldGenerateTheExpectedUppercaseValue(String input, String expected) {
        String actualValue = input.toUpperCase();
        assertEquals(expected, actualValue);
    }

CsvFileSource: CSV 파일을 참조하여 데이터를 전달한다.

  • delimiter 속성으로 사용자 정의할 수 있다.
  • numLinesToSkip 속성으로 CSV 파일을 읽을 때 줄을 건너뛸 수 있다.
  • encoding 속성을 사용하여 인코딩 값을 사용자 정의할 수 있다.
@ParameterizedTest
    @CsvFileSource(resources = "/data.csv", numLinesToSkip = 1)
    void toUpperCase_ShouldGenerateTheExpectedUppercaseValueCSVFile(String input, String expected) {
        String actualValue = input.toUpperCase();
        assertEquals(expected, actualValue);
    }

Complex Values

MethodSource: 메서드를 매개변수로 전달한다.

  • MethodSource에 이름을 제공하지 않으면 JUnit은 테스트 메서드와 동일한 이름을 가진 소스 메서드를 검색하여 전달한다.
private static Stream<Arguments> provideStringsForIsBlank() {
        return Stream.of(
            Arguments.of(null, true),
            Arguments.of("", true),
            Arguments.of("  ", true),
            Arguments.of("not blank", false)
        );
    }

@ParameterizedTest
    @MethodSource("provideStringsForIsBlank")
    void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input, boolean expected) {
        assertEquals(expected, Strings.isBlank(input));
    }

FieldSource: 정적 필드를 전달한다.

  • JUnit 5.11부터 지원된다.
  • 테스트 데이터를 정적 필드로 관리하고 재사용할 수 있다.
  • 정적 필드의 이름이 테스트 이름과 일치하면 주석의 을 생략할 수 있다.
static List<String> cities = Arrays.asList("Madrid", "Rome", "Paris", "London");

    @ParameterizedTest
    @FieldSource("cities")
    void isBlank_ShouldReturnFalseWhenTheArgHasAtLeastOneCharacter(String arg) {
        assertFalse(Strings.isBlank(arg));
    }

디스플레이 이름 사용자 정의

  • @ParameterizedTest 애노테이션의 name 속성을 통해 테스트의 디스플레이 이름을 사용자 정의할 수 있다.
  • 각 테스트 인스턴스의 결과를 명확하게 표현할 수 있다.

자리 표시자

  • {displayName}: 메서드의 표시 이름으로 대체된다. 만약 @Display 애노테이션이 제공된 경우, 해당 값이 사용된다.
  • {index}: 호출 인덱스로 대체된다. 첫 번째 실행의 호출 인덱스는 1, 두 번째는 2, 이런 식으로 계속된다.
  • {arguments}: 인수 값의 전체 목록을 쉼표로 구분하여 나타내는 자리 표시자이다.
  • {argumentsWithName}: 명명된 인수에 대한 자리 표시자이다. arguments(named(NAME, ARG), …) 구조로 생성된다. 주어진 이름과 실제 매개변수 이름을 출력한다.
  • {argumentSetName}: 팩토리 메서드 argumentSet에 제공된 첫 번째 매개변수(세트의 이름)에 대한 자리 표시자이다.
  • {argumentSetNameOrArgumentsWithName}: argumentSet에 제공된 첫 번째 매개변수에 대한 자리 표시자이다.
  • {0}, {1}, …: 개별 인수에 대한 자리 표시자이다.

예제 코드

@ParameterizedTest(name = "{index} - {0} is 30 days long")
@EnumSource(value = Month.class, names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"})
void someMonths_Are30DaysLong(Month month) {
    final boolean isALeapYear = false;
    assertEquals(30, month.length(isALeapYear));
}

위로 스크롤