목차
- 서론
- @ParameterizedTest란?
- 예제 코드 -@ValueSource
- 파라미터를 여러개 받고 싶다면? - @CsvSource
- 사용자 정의 클래스를 파라미터로 넣고 싶다면? - @MethodSource
- 테스트 코드에 설명 덧붙이기
👀 서론
테스트 코드를 작성하다 보면 코드가 반복되는게 느껴질 때가 있다.
개인적으로 나는 객체가 잘 생성되는지 확인할 때도 경계값 중간값까지 최소 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) {
// ...
}
이제 확실히 뭘 테스트하고 싶었던건지 확인이 가능하다.
참고
- JUnit5 docs - https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests
- 우테코 문서 - 자동차 경주 / 학습테스트를 통한 JUnit 학습하기
- stackoverflow - https://stackoverflow.com/questions/56691782/what-is-the-use-of-valuesourceclasses
- baeldung - https://www.baeldung.com/junit-testinstance-annotation
'Develop > Java' 카테고리의 다른 글
[Java] 접근 권한을 최소화해야 하는 이유 (0) | 2022.03.06 |
---|---|
[Java] Predicate란? (4) | 2022.03.04 |
[Java] 인스턴스화 방지를 위해 private 생성자 이용하기 (3) | 2022.02.26 |
[Java] EnumMap이란? (4) | 2022.02.25 |
[Java] ConcurrentModificationException 에러 (0) | 2022.02.24 |