Spring/Test-Driven Develop

[JUnit] JAVA 매개변수 Test - @ParameterizedTest

민돌v 2022. 9. 29. 09:48

 

🚀 오늘은 다양한 매개변수 케이스를 하나의 테스트 메소드에서 주입시켜주는
@ParameterizedTest 어노테이션의 다양한 케이스들의 가이드를 기록해보고자 합니다...!!

 

[목차]

  1. @Parameterized란
  2. @Parameterized 가이드
  3. @Parameterized 공급자 종류

 


@Parameterized란

  • @ParameterizedTest 어노테이션은 Junit 에서 다양한 매개변수들에 대한 경우를 테스트할 수 있도록 제공해주는 어노테이션입니다.
  • @ParameterizedTest 주석이 달린 메소드는 매개변수화된 테스트 메소드임을 의미하고 Private, Static 이 아니어야 합니다.

 


 

@Parameterized 시작 가이드 

의존성 추가

  • 먼저 JUnit Jupiter Params 의존성을 추가해야 @ParameterizedTest 를 사용할 수 있습니다.

 

Gradle - build.gradle

testCompile("org.junit.jupiter:junit-jupiter-params:5.7.0")

 


 

@Parameterized 다양한 공급자들

  • @ParameterizedTest 메소드는 @ArgumentsSource 또는 이에 사응하는 합성 주석(ex - @ValueSource, @CsvSource 등) 을 통해 하나 이상의  ArgumentsProvider 를 지정해주어야합니다.
  • 공급자는 매개 변수화된 테스트 메소드를 호출하는데 사용할 Argument Strem 을 반환해야할 책임이 있습니다.
  • ✨ 즉, @Parameterized 를 사용한다면, 꼭 어떤 매개변수를 사용해 줄 것인지(@ArgumentsSource 들) 주입해주어야한다는 뜻입니다!

 

junit param 패키지

 


Provider

위에서도 말했다시피 @Parameterized 를 사용하기 위해서는 매개변수(실제값을 담은 argument라 칭함) 를 넘겨주어야하고 공식문서에서는 이를 ArgumentsProvider 라 칭하고 있습니다.

이 ArgumentProvider 로 사용될 수 있게 주석으로 만들어놓은 것이 argument source 들이고 ArgumentProvider로 사용할 수 있는 arguments source 는 @ArgumentsSoruce로 등록할 수 있다고 합니다.

 

argument source 들의 종류

  • ValueSource
  • MethodSource
  • NullAndEmptySource
  • CsvSource
  • CsvFileSource
  • EnumSource
  • 등등..

 


@Parameterized - Argument Source 가이드

인자 @Parmaterized를 직접적으로 사용해서 테스트해보는 방법을 알아보겠습니다.

 

1. @ValueSource

  • @ValueSource는 리터럴 값 배열에 대한 액세스를 제공하는  ArgumentsSource 입니다.
  • 지원되는 유형에는 shorts , bytes , ints , longs , floats , doubles , chars , booleans , strings 및 classes 가 포함됩니다. 
  • @ValueSource 는 하나의 인자를 받을 때 유용하게 사용됩니다. String을 제외한 원시타입 변수들을 주입받을 수 있습니다.
  • @ValueSource를 사용할때는 2번째 문장처럼, 소문자에 "s" 를 붙여서 타입을 명시합니다.

 

@ParameterizedTest
@ValueSource(strings = {"1", "2", "3", "0", "21332"})
public void sizeOne(final String numbers) {
    //given
    String[] input = {numbers};
    
    //when
    Calculator calculator = new Calculator();
    
    //then
    assertThat(calculator.strSum(input)).isEqualTo(Integer.valueOf(numbers));
}

 

2. @MethodSource

  • @MethodSource는 이 주석이 선언된 클래스의 팩토리 메소드 또는 정규화된 메소드 이름이 참조하는 외부 클래스의 정적 팩토리 메소드에서 리턴된 값에 대한 액세스를 제공하는 ArgumentSouce 입니다.
  • @ValueSource 에서는 다루지 못하는 Object를 매개변수로 주입받을 수 있습니다.
  • Stream<Arguments> 를 반환하는 별도의 정적 메소드가 필요합니다.
  • @MethodSource("주입해주는 메소드 명") 으로 명시적으로 선언해주어도 되고, 아래 예시처럼 메소드 명이 같으면 자동으로 주입되기도 합니다.

 

@ParameterizedTest
@MethodSource
@DisplayName("계산기는 배열로 들어온 음수 or 숫자가 아니면 예외를 던진다.")
void calculator_sum_x(String[] numbers) {

    Calculator calculator = new Calculator();

    //then
    assertThatThrownBy(() -> calculator.strSum(numbers))
        .isInstanceOf(RuntimeException.class);

}

private static Stream<Arguments> calculator_sum_x() {
    return Stream.of(
        Arguments.of((Object) new String[]{"-1"}),
        Arguments.of((Object) new String[]{"1,-1"}),
        Arguments.of((Object) new String[]{"dk"}),
        Arguments.of((Object) new String[]{"0", "1", "adf"}),
        Arguments.of((Object) new String[]{"1", "df", "-1"}),
        Arguments.of((Object) new String[]{"A", "B", "C", "D", "E"})
    );
}

 

 

3. @NullAndEmptySource

  • @NullAndEmptySource는 파라미터에 null 값을 주입해주는 @NullSource 와 빈값을 주입하는 @EmptySource 의 기능을 합친 주석입니다.
  • 아래처럼 사용하면 코드의 중복을 족흠은 줄일수 있습니다

2가지 경우를 모두 테스트하고, 명시적으로 선언해줄 떄

@Test
@DisplayName("들어온 값이 빈값일 경우 false")
void preEmptyCheck() {
    //given
    String input1 = "";
    Input input = new Input();
    assertThat(input.isEmpty(input1)).isFalse();
}

@Test
@DisplayName("들어온 값이 null일 경우 false")
void preNullCheck() {
    //given
    String input1 = null;
    Input input = new Input();
    assertThat(input.isEmpty(input1)).isFalse();
}

@NullAndEmptySource를 사용할 때 - 코드 확 짧아져버림

@ParameterizedTest
@NullAndEmptySource
@DisplayName("들어온 값이 null 이거나 빈 값일 경우")
void preBlankCheck(String input1) {
    Input input = new Input();
    assertThat(input.isEmpty(input1)).isFalse();
}

2가지 케이스가 주입됨

 

 

4. @CsvSource

  • @CsvSource 는 value 속성 또는 textBlock 속성을 통해 제공된 하나 이상의 CSV 행에서 쉼표(,)로 구분된 값(CSV)를 읽는 ArgumentSource 입니다.
  • 구분자는 delimter() 를 이용하여 커스텀하게 지정할 수 있습니다.
  • 👏🏻 쉽게 말해 @CsvSource 는, 테스트할 파라미터 값과, 그 값에 따른 결과값 까지 변경해서 더 다양하게 테스트가 가능합니다.

 

숫서대로 파라미터에 들어가는 @CsvSource

@ParameterizedTest
@CsvSource({
      "apple,         1",
      "banana,        2",
      "'lemon, lime', 0xF1",
      "strawberry,    700_000",
})
void test(String fruit, int rank) {
      // ...
}

구분자를 커스텀하게 지정해 줄 수 있고, 결과값까지 주입받아서 다양한 테스트가 가능

@ParameterizedTest
@CsvSource(value = {"1:true", "2:true", "3:true", "4:false", "5:false"}, delimiter = ':')
void set_contains_true_false(int element, boolean expected) {
	assertThat(numbers.contains(element)).isEqualTo(expected);
}

 

 

5. @CsvFileSource

  • @CsvFileSource 는 하나 이상의 클래스 경로 resources 또는 files 에서 쉼표로 구분된 값(CSV) 파일을 로드하는데 사용되는 ArgumentsSource 입니다.
  • #기호로 시작하는 줄은 주석으로 해석되어 무시된다고 합니다.
  • 👏🏻 쉽게말해, resource 폴더 경로에있는 .csv 파일을 로드하여 구분자로 넣어줍니다.
  • 대용량 테스트 처리에 용이해보입니다.

 

정의된 .csv 파일 예시

//two-column.csv
Country, reference
Sweden, 1
Poland, 2
"United States of America", 3

@CsvSource 예시

@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1)
void testCsvFileSourceResource(String country, int reference) {
}

@ParameterizedTest
@CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1)
void testCsvFileSourceFiles(String country, int reference) {
}

 

 

6. @EnumSource

  • @EnumSource 는 Enum 의 상수에 대한 ArgumentsSource 입니다.
  • enum 유형은 Value 속성을 사용하여 명시적으로 지정할 수 있고, 그렇지 않으면 @ParameterizedTest 메소드의 첫 번째 매개변수의 선언된 유형이 사용됩니다.
  • 열거형 상수 집합은 names 와 mode 속성을 통해 제한할 수 있습니다.
@ParameterizedTest
@EnumSource(Month.class) // passing all 12 months
void getValueForAMonth_IsAlwaysBetweenOneAndTwelve(Month month) {
    int monthNumber = month.getValue();
    assertTrue(monthNumber >= 1 && monthNumber <= 12);
}
@ParameterizedTest
@EnumSource(value = Month.class, names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"})
void someMonths_Are30DaysLong(Month month) {
    final boolean isALeapYear = false;
    assertEquals(30, month.length(isALeapYear));
}

 

@ParameterizedTest
@EnumSource(
    value = Month.class,
    names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER", "FEBRUARY"},
    mode = EnumSource.Mode.EXCLUDE)
void exceptFourMonths_OthersAre31DaysLong(Month month) {
    final boolean isALeapYear = false;
    assertEquals(31, month.length(isALeapYear));
}

 

 

 

 

 

 

 


*참고