[JUnit] @ParameterizedTest로 중복되는 테스트 표현 제거하기

November 08, 2023


일반적인 Test

@DisplayName("문자가 주어졌을 때, 대문자 여부 반환하는 함수 성공 테스트")
@Test
void givenString_whenCheckCapitalWord_thenReturnCorrectResult() {
    // given
    char input = 'A';
    boolean expected = true;

    // when
    boolean isCapital = Character.isUpperCase(input);

    // then
    assertThat(isCapital).isEqualTo(expected);
}

테스트하고자 하는 경우를 한 번만 테스트할 수 있습니다.

ParameterizedTest

@ParameterizedTest를 사용하면 하나의 테스트 함수로 여러 테스트 케이스를 사용할 수 있습니다.

MethodSource

@DisplayName("문자가 주어졌을 때, 대문자 여부 반환하는 함수 테스트")
@MethodSource("provideCharacter")
@ParameterizedTest
void givenString_whenCheckCapitalWord_thenReturnCorrectResult(char input, boolean expected) {
    boolean isCapital = Character.isUpperCase(input);
    assertThat(isCapital).isEqualTo(expected);
}

static Stream<Arguments> provideCharacter() {
    return Stream.of(
            Arguments.of("H", true),
            Arguments.of("w", false)
    );
}

주어진 문자가 대문자인지 여부를 반환하는 함수를 테스트하는 코드입니다.

테스트하기 위해서 문자와 그에 해당하는 올바른 예상 결과가 필요합니다. 그래서 provideCharacter 함수에서 Arguments를 한 쌍으로 만들어서 스트림 형태로 반환합니다.

MethodSource에너테이션에서 provideCharacter를 지정하여 테스트할 인자들을 여러 개 반복해서 테스트할 수 있습니다.

CsvSource

@DisplayName("문자가 주어졌을 때, 대문자 여부 반환하는 함수 테스트")
@CsvSource(value = {"a,false", "b,false", "C,true"})
@ParameterizedTest
void givenString_whenCheckCapitalWord_thenReturnCorrectResult(char input, boolean expected) {
    boolean isCapital = Character.isUpperCase(input);
    assertThat(isCapital).isEqualTo(expected);
}

CsvSource의 경우, 따로 함수를 만들어서 사용하지 않고, value에 값을 지정하는 것을 볼 수 있습니다.

기본인 ,가 아니라 delimiter를 지정해서 csv의 구분자를 변경할 수도 있습니다.

외부 CSV 파일 사용하기

위의 테스트 코드에서는 인자값들을 하드 코딩하여 테스트 코드 클래스 파일안에 작성했습니다.

그런데 하드 코딩된 테스트 케이스를 사용하고 싶지 않다면, 외부에서 csv 파일을 읽어와서 사용할 수 있습니다.

외부에서 파일 읽어서 사용하고 싶다면, 아래와 같이 csv 파일을 만들어 test/resources 폴더 하단에 위치하면 됩니다.

  • 디렉터리 구조

    .
    ├── java
    │   └── CapitalTest.java
    └── resources
        └── sample-1.csv
  • sample-1.csv

    input,result
    Hello,true
    world,false
    Meow,true

files로 파일 경로를 지정

numLinesToSkip로 건너뛸 행의 수를 지정 가능

@DisplayName("문자열이 주어졌을 때, 대문자로 시작 여부를 반환하는 함수 성공 테스트")
@CsvFileSource(files = "src/test/resources/sample-1.csv", numLinesToSkip = 1)
@ParameterizedTest
void givenText_whenCheckStartWithCapital_returnCorrectResult(String input, boolean expected) {
    final boolean actual = isStartWithCaplital(input);
    assertThat(actual).isEqualTo(expected);
}

리스트 인자

그런데 만약 CSV 파일에서 여러 개의 숫자로 이루어진 리스트를 넘기고 싶으면 어떻게 할까요?

이와 같이 한 열을 읽어서 여러 개의 요소들로 이루어진 리스트를 반환하고 싶다면 @ConvertWith을 사용할 수 있습니다.

예제 테스트로는 여러 나이가 주어질 때, 성인이 몇 명인지를 반환하는 함수를 테스트해보겠습니다.

  • sample-2.csv

    나열된 값들을 "로 감싸서 하나의 열임을 구분합니다.

    person ages,adult count
    "23,10,43,12",2
    "45",1

테스트

ArgumentConverter를 상속하는 SplitByCommaConverter를 구현합니다.

지금 예제는 한 행을 읽어서 나이인 숫자들을 리스트로 반환하기 때문에, 문자열을 숫자 리스트로 변환하면 됩니다.

public class SplitByCommaConverter implements ArgumentConverter {

    @Override
    public Object convert(Object source, ParameterContext context)
            throws ArgumentConversionException {
        if (!(source instanceof String)) {
            throw new IllegalArgumentException(
                    "The argument should be a string: " + source);
        }
        String input = (String) source;
        return convert(input);
    }

    private List<Integer> convert(String input) {
        String[] parts = input.split(","); // 구분할 delimiter 지정
        return Arrays.stream(parts)
                .map(Integer::parseInt)
                .collect(toList());
    }
}

테스트 함수

@CsvFileSource(files = "src/test/resources/sample-2.csv", numLinesToSkip = 1)
@ParameterizedTest
void givenAges_whenCountAdult_returnValidAdultCount(
    @ConvertWith(SplitByCommaConverter.class) List<Integer> ages,
    int expectedCount
) {
    final int count = countAdult(ages);
    assertThat(count).isEqualTo(expectedCount);
}

이전에 만든 SplitByCommaConverter 클래스를 알맞은 타입 앞에 사용합니다.

@ConvertWith(SplitByCommaConverter.class) List<Integer> ages로 여러 값들을 리스트 형식으로 변환해서 인자로 쓸 수 있습니다.

이 예제에서는 리스트 타입으로 값을 변환했지만, 비즈니스 도메인 객체나 원하는 타입대로 변환해서 사용할 수도 있을 것입니다.

정리

@ParameterizedTest를 사용해, 테스트에 필요한 정보들을 하드 코딩하지 않고 외부의 파일으로 반복적인 테스트를 하나의 테스트 함수로 확인할 수 있음을 볼 수 있었습니다.

@ConvertWith으로 원하는 타입으로 미리 값을 변환할 수 있습니다. 이로써 변환 로직은 변환 클래스에서 담당하고 테스트 코드는 오로지 테스트에만 집중할 수 있습니다.


Profile picture

이재원

이해하기 쉬운 코드를 작성하려 고민합니다.


© 2024 Won's blog Built with Gatsby