Develop/Java

[JUnit5] 중복되는 테스트 코드 줄이기

연로그 2022. 3. 2. 19:29
반응형

목차

  1. 서론
  2. @ParameterizedTest란?
    • 예제 코드 -@ValueSource
    • 파라미터를 여러개 받고 싶다면? - @CsvSource
    • 사용자 정의 클래스를 파라미터로 넣고 싶다면? - @MethodSource
  3. 테스트 코드에 설명 덧붙이기

 


👀 서론

테스트 코드를 작성하다 보면 코드가 반복되는게 느껴질 때가 있다.

개인적으로 나는 객체가 잘 생성되는지 확인할 때도 경계값 중간값까지 최소 3번은 테스트하는 편이다.

값이 바뀔때마다 코드를 복붙하는게 불필요하게 느껴져서 테스트 코드 내부에 메소드를 따로 만들기도 했었는데...

우테코를 통해 @ParameterizedTest라는 어노테이션을 알게 되었다.

 

불 - 편 -

 


💡 @ParameterizedTest란?

  • 다양한 인수로 테스트를 여러번 실행할 수 있도록 해주는 어노테이션
  • @Test 어노테이션 대신 사용
  • 최소 하나의 source 어노테이션 필요
    ex: @ValueSource, @CsvSource, @MethodSource, ...

 

📚 예제 코드 - @ValueSource

@ParameterizedTest
@ValueSource(strings = {"a","b"})
public void test(String strring) {
    System.out.println(string);
}​
  • 다른 타입도 지원
    ex: strings, shorts, ints, booleans, bytes, chars, doubles, floats, longs, classes

 

❓ 파라미터를 여러개 받고 싶다면?

여러 파라미터를 동시에 받고 싶은 경우가 있을 수 있다.

source 어노테이션을 동시에 쓰면 되지 않을까? 라는 생각에 아래와 같은 코드를 작성해보았다.

// bad code
@ParameterizedTest
@ValueSource(strings = {"1", "2"})
@EnumSource(Ranking.class)
public void test(String str, Ranking ranking) {
    System.out.println(str);
    System.out.println(ranking);
}

어림도 없지

 

org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [파라미터 타입 arg1] in method [메소드(파라미터 타입)]. 에러가 발생한다.

주입 받아야하는 파라미터가 등록이 불가능한 현상이다.

게다가 좌측을 보니 동시에 받아진 것도 아니고 @ValueSource의 값들을 다 받은 후 @EnumSource의 값들을 하나씩 호출되고 있다.

 

이런 경우에는 @CsvSouce 어노테이션을 활용하면 된다.

// good code
@ParameterizedTest
@CsvSource(value = {"test1:FIRST", "test2:SECOND"}, delimiter = ':')
public void test(String str, Ranking ranking) {
    System.out.println(str + ranking);
}

성공!

 

❓ 사용자 정의 클래스를 파라미터로 넣고 싶다면?

@ValueSource에는 strings, ints 뿐만 아니라 classes도 존재한다.

아, 이걸 이용해서 사용자 정의 클래스를 파라미터로 받는건가? 하고 단단히 착각했다.

실행이고 뭐고 빨간줄 부터 없애봐...

 

🔻 @ValueSource의 classes

더보기
@ParameterizedTest
@ValueSource(classes = {String.class, Integer.class})
void test(Class<?> argument) {
    System.out.println(argument);
}

코드를 보면 딱 알겠지만 특정 인스턴스가 아닌 class를 파라미터로 넣을 수 있게 된다.

 

사용자 정의 클래스를 사용하고 싶다면 다른 방법을 이용해야 한다.

@MethodSource를 활용한 예제를 보자.

@ParameterizedTest
@MethodSource("parameterProvider")
void test(LottoNumber lottoNumber) {
    System.out.println(lottoNumber);
}

private static Stream<Arguments> parameterProvider() {
    return Stream.of(
            Arguments.arguments(LottoNumber.of(1)),
            Arguments.arguments(LottoNumber.of(2)),
            Arguments.arguments(LottoNumber.of(3))
    );
}

 

뿐만 아니라 여러개의 파라미터를 정의할 수도 있다.

@ParameterizedTest
@MethodSource("parameterProvider")
void test(LottoNumber lottoNumber, String str, int num) {
    System.out.println(lottoNumber + str + num);
}

private static Stream<Arguments> parameterProvider() {
    return Stream.of(
            Arguments.arguments(LottoNumber.of(1), "1", 1),
            Arguments.arguments(LottoNumber.of(2), "2", 2),
            Arguments.arguments(LottoNumber.of(3), "3", 3)
    );
}

 

🔻 @MethodSource 더 알아보기

더보기
  • 특정 메소드가 반환하는 값을 자원으로 삼는 것
  • 테스트 클래스 내의 static 메소드 사용
    또는 테스트 클래스 상단에 @TestInstance(Lifecycle.PER_CLASS) 추가
  • 하나 이상의 파라미터

 

@TestInstance(Lifecycle.PER_CLASS)란?

👉 생명 주기를 클래스 단위로 설정 (디폴트 설정은 메소드 단위)

클래스의 인스턴스를 단 하나만 만들고 테스트 간의 재사용을 요청할 수 있게 함

 

파라미터를 하나만 생성하는 경우는 메소드를 아래와 같이도 쓸 수 있다.

(반환 타입이 Stream<자료형>)

private static Stream<Integer> parameterProvider() {
    return Stream.of(1, 2, 3);
}

 


3. 테스트 코드에 설명 덧붙이기

(22/03/05 18:17 추가)

 

위 주제를 블로그 포스팅 스터디에서 발표를 했다.

감사하게도 다른 스터디원 분이 테스트 코드의 설명을 덧붙이는 방법에 대해 리뷰해주셔서 추가한다.

 

@Parameterized의 name 속성을 이용하면 설명을 추가할 수 있다.

굳이 설명을 추가해야하나? 싶은 사람들을 위해 추가 전 실행 결과 화면을 가져왔다.

대충 Ranking 클래스의 findRanking 메소드를 테스트한 것 같은데..

6은 뭐고 false는 뭐고 FIRST는 뭐지? 뭘 의미하는거지?

 

만약 사용자 정의 객체 등을 파라미터로 받는 경우는 더 심각하다

lotto.domian.LottoNumbers@36b8bb47이 뭘 의미하는 객체인지 알아볼 수 있는 사람?

 

이번엔 @ParameterizedTest의 name 속성에 다음과 같은 설명을 추가해보았다.

  • {index}: 1부터 카운트되는 인덱스
  • {0}: 0번째 위치하는 파라미터
@ParameterizedTest(name = "[{index}] 일치 개수가 {0}개면 Ranking은 {2}")
@MethodSource("parameterProvider")
void findRanking(int count, boolean isBonus, Ranking expect) {
    // ...
}

이제 확실히 뭘 테스트하고 싶었던건지 확인이 가능하다.


참고

  1. JUnit5 docs - https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests
  2. 우테코 문서 - 자동차 경주 / 학습테스트를 통한 JUnit 학습하기
  3. stackoverflow - https://stackoverflow.com/questions/56691782/what-is-the-use-of-valuesourceclasses
  4. baeldung - https://www.baeldung.com/junit-testinstance-annotation
반응형