티스토리 뷰
예림언니와 페어 프로그래밍으로 테스트 코드를 작성하던 중 멘토님께서 알려주신 Prameterized Test를 적용해 보기로 했다.
사용자 이름에 1자 ~ 30자 제한이 있는 상황에서, 사용자 이름이 범위를 초과해 실패하는 테스트를 짜려고 @ValueSource(strings = {"", "a".repeat(31)})를 썼는데, @ValueSource에는 "a".repeat(31)와 같은 메서드는 사용할 수 없었다. 일단은 @ValueSource(strings = {"a", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"})로 사용을 했는데, 아무리 생각해도 이 문제를 해결할 수 있는 기능이 있을 것 같다는 강한 직감이 왔다..😂😂
그리고
├─ 이름을 2 ~ 30자 범위를 초과 입력해 유저 생성에 실패한다.
│ │ ├─ [1] a
│ │ └─ [2] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
이렇게 불친절한 Display Name도 좀 수정을 하고 싶어졌다..
그래서 이왕 찾아보는 김에 parameterized test에 대해 정리를 해보려 한다!
❓ parameterized test?
: JUnit 5에 새로 추가된 기능으로, 다양한 매개변수로 단일 테스트 메서드를 여러 번 실행할 수 있는 기능이다.
간단한 예시를 살펴보자
public class Numbers {
public static boolean isOdd(int number) {
return number % 2 != 0;
}
}
위와 같은 메서드가 있을 때, 아래와 같이 Parameterized 테스트 코드를 작성할 수 있다.
@ParameterizedTest
@ValueSource(ints = {1, 3, 5, -3, 15, Integer.MAX_VALUE})
void isOdd_ShouldReturnTrueForOddNumbers(int number) {
assertTrue(Numbers.isOdd(number));
}
이런식으로 @ParameterizedTest 애너테이션을 붙인 뒤, @ValueSource 애너테이션으로 매개변수 값을 지정해주면
테스트 메서드의 매개변수에 설정해 준 값이 차례대로 들어간다! 그리고 해당 테스트 메서드는 총 6번 호출된다.
간단한 예시들과 함께 사용 방법을 익혀보자!
의존성 추가
1. Maven 사용 시 pom.xml에 다음을 추가
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
2. Gradle 사용 시 build.gradle에 다음을 추가
testCompile("org.junit.jupiter:junit-jupiter-params:5.10.0")
SpringBoot 2.2.0 이상을 사용하고 Spring-Web 의존성을 추가했다면, JUnit 5가 기본적으로 적용되어 있어 의존성 추가 과정을 생략 해도 된다.
파라미터 정보를 지정해주자 - Argument Sources
1️⃣ 기본형과 String, Class
@ValueSource(strings = {"", " "})
@ValueSource(ints = {1, 3, 5, -3, 15, Integer.MAX_VALUE})
위와 같이 @ValueSource 애너테이션과 함께 문자열 값을 파라미터로 넘길 수 있다.
String을 넘기는 strings 외에도
- shorts, bytes, ints, longs, floats, doubles, chars, strings, classes를 사용할 수 있다!
❗@ValueSource 애너테이션으로는 null을 지정할 수 없다.
2️⃣ Null과 빈 값
@NullSource
@EmptySource
@NullAndEmptySource
@EmptySource를 사용하면 String 타입의 매개변수에 빈 문자열을 제공하거나 컬렉션 및 배열에 빈 값을 제공할 수 있다.
❗기본형 파라미터를 가진 테스트 메서드에는 @NullSource를 사용할 수 없다.
3️⃣ 열거형(Enum)
@EnumSource(Month.class)
@EnumSource(
value = Month.class,
names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"})
@EnumSource(
value = Month.class,
names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER", "FEBRUARY"},
mode = EnumSource.Mode.EXCLUDE)
@EnumSource(value = Month.class, names = ".+BER", mode = EnumSource.Mode.MATCH_ANY)
▪️ mode 속성을 EnumSource.Mode.EXCLUDE로 두어서 names에 적힌 값을 제외한 나머지를 파라미터로 넘길 수 있다.
▪️ mede 속성을 EnumSource.Mode.MATCH_ANY로 두어서 names에 정규식을 작성할 수 있다.
파라미터를 여러개 전달 해보자
4️⃣ CSV 리터럴
@CsvSource({"test,TEST", "tEst,TEST", "Java,JAVA"})
@CsvSource(value = {"test:test", "tEst:test", "Java:java"}, delimiter = ':')
@CsvSource에는 쉼표로 구분된 값의 배열을 적을 수 있다. 각 배열의 항목이 CSV 파일의 한 줄에 해당한다.
@CsvSource를 사용하면 매번 하나의 배열 항목을 가져와 쉼표로 나누고 별도의 매개변수로 전달한다.
▪️ delimiter 속성을 사용하여 구분자를 지정할 수 있다.
예시로 살펴보자.
String의 toUpperCase() 메서드를 테스트 하기 위해서는 단순히 @ValueSource(strings = {"test", "tEst", "Java"})를 사용해서는 안된다. 예상되는 결과 값("TEST", "TEST", "JAVA")도 함께 필요하기 때문! 두개의 파라미터를 제공하기 위해서 @CsvSource를 사용할 수 있다.
@ParameterizedTest
@CsvSource({"test,TEST", "tEst,TEST", "Java,JAVA"})
void toUpperCase_ShouldGenerateTheExpectedUppercaseValue(String input, String expected) {
String actualValue = input.toUpperCase();
assertEquals(expected, actualValue);
}
5️⃣ CSV 파일
@CsvFileSource(resources = "/data.csv", numLinesToSkip = 1)
▪️ resources 속성에 CSV 파일의 경로를 지정해 줄 수 있다.
▪️ numLinesToSkip 속성을 통해 CSV 파일을 읽을 때 건너뛸 줄 수를 나타낼 수 있다. (헤더를 건너뛰는 데 유용)
▪️ lineSeparator 속성을 통해 줄 구분자를 지정할 수 있다. (기본값이 개행)
▪️ encoding 속성을 통해 인코딩 형식을 지정할 수 있다. (기본이 UTF-8)
6️⃣ 메서드
@ParameterizedTest
@MethodSource("provideStringsForIsBlank")
void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input, boolean expected) {
assertEquals(expected, Strings.isBlank(input));
}
@MethodSource에 제공하는 name은 사용하려는 메서드명과 일치해야 한다.
즉, 아래와 같은 메서드가 존재해야 한다.
private static Stream<Arguments> provideStringsForIsBlank() {
return Stream.of(
Arguments.of(null, true),
Arguments.of("", true),
Arguments.of(" ", true),
Arguments.of("not blank", false)
);
}
매개변수를 하나만 전달하고 싶다면 Arguments를 사용할 필요없이 다음과 같이 사용할 수 있다.
@ParameterizedTest
@MethodSource("provideLongString")
void isLoggerThan30(String input) {
assertTrue(input.length() > 30);
}
private static List<String> provideLongString() {
String string = "long name ";
return List.of(string.repeat(4));
}
그 외에도 ArgumentsProvider를 구현해 @ArgumentsSource() 을 사용하거나, 커스텀 애너테이션을 만들수도 있다. 하지만 이런 기능은 있다는 것만 알아두고 나중에 필요할 때 더 공부해보자!
마지막으로..
Display Name을 바꿔보자
만약 아래와 같이 Parameterized Test에 @DisplayName를 설정 했다면,
@DisplayName("홀수를 입력하면 true를 리턴한다.")
@ParameterizedTest
@ValueSource(ints = {1, 3, 5, -3, 15, Integer.MAX_VALUE})
void isOdd_ShouldReturnTrueForOddNumbers(int number) {
assertTrue(Numbers.isOdd(number));
}
기본적으로 아래와 같은 Display Name이 표시된다.
├─ 홀수를 입력하면 true를 리턴한다.
│ │ ├─ [1] 1
│ │ ├─ [2] 3
│ │ ├─ [3] 5
│ │ ├─ [4] -3
│ │ ├─ [5] 15
│ │ └─ [6] 2147483647
Display Name이 너무 불친절하다. 더 자세한 내용을 표시하도록 변경해보자!
@DisplayName("홀수를 입력하면 true를 리턴한다.")
@ParameterizedTest(name = "{index}. {0}은 홀수이다.")
@ValueSource(ints = {1, 3, 5, -3, 15, Integer.MAX_VALUE})
void isOdd_ShouldReturnTrueForOddNumbers(int number) {
assertTrue(Numbers.isOdd(number));
}
위와 같이 @ParameterizedTest의 name 속성을 통해 사용자 정의 Display Name을 설정 할 수 있다.
그리고 {index}와 같이 name에서 사용할 수 있는 placeholder가 몇가지 있다.
- {index} : 파라미터의 호출 순서
- {arguments} : 모든 파라미터(인자) 리스트
- {0}, {1}, ... : 개별 파라미터(인자)
├─ 홀수를 입력하면 true를 리턴한다.
│ │ ├─ 1. 1은 홀수이다.
│ │ ├─ 2. 3은 홀수이다.
│ │ ├─ 3. 5는 홀수이다.
│ │ ├─ 4. -3은 홀수이다.
│ │ ├─ 5. 15는 홀수이다.
│ │ └─ 6. 2147483647은 홀수이다.
이렇게 Display Name이 잘 변경된 것을 볼 수 있다!
참고링크
📎 Junit 5 공식문서 - parameterized tests
JUnit 5 User Guide
Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo
junit.org
📎 벨덩(Baeldung) - Guide to JUnit 5 Parameterized Tests
'백엔드 > Spring' 카테고리의 다른 글
Spring REST Docs를 적용해보자! (1) | 2023.11.21 |
---|---|
스프링에서 로깅을 해보자📝 (0) | 2023.10.17 |
스프링 프레임워크의 핵심 개념들 (2) | 2023.10.10 |
[오류] JPAQueryFactory could not be found (0) | 2023.07.25 |
[스프링] 단축키 모음 (0) | 2023.07.07 |