테스트 코드를 작성하던 중, 한 기능에서 로직은 같지만 입력 파라미터만 다른 테스트케이스를 작성해야 할 일이 있었다. 코드는 중복되지만, 각 케이스에 대한 검증은 필요하므로 테스트 코드를 작성하지 않을 수는 없다.
이러한 경우 조금 더 코드를 간결하게 작성하는 방법이 있지 않을까 고민해보고 찾아보는 중 Junit5에서 제공하는 @ParameterizedTest
라는 것을 알게 되었다.
@ParameterizedTest는 하나의 테스트를 여러 번 돌려야 할 때 사용한다.
@ParameterizedTest
를 명시한다.@ValueSource
를 이용해 명시해준다. (short, byte, int, long, float, double, char, String, Class 명시 가능)import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class TestControllerTest {
@ParameterizedTest
@ValueSource(strings = {"test1", "test2", "test3"})
void test(String value) {
System.out.println(value);
}
}
이 테스트 코드의 실행 결과는 다음과 같다.
하지만, 이 방법은 파라미터를 하나만 전달할 때만 가능하며 두 개 이상의 파라미터는 전달할 수 없다.
@MethodSource
를 이용하면 두 개 이상의 파라미터 또한 전달받을 수 있도록 할 수 있다.
import java.util.stream.Stream;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class TestControllerTest {
// test method
@ParameterizedTest
@MethodSource("provideKeyAndValue")
void test(Integer key, String value) {
System.out.println(key + " " + value);
}
//source method
private static Stream<Arguments> provideKeyAndValue() {
return Stream.of(
Arguments.of(1, "value1"),
Arguments.of(2, "value2"),
Arguments.of(3, "value3")
);
}
}
단, 이를 사용하기 위해서는 source method가 static이어야 한다.
하지만, 성격이 같은 테스트케이스끼리 묶을 경우 종종 inner class를 사용하는 경우가 있고, inner class에서는 static method를 선언하지 못한다.
import java.util.stream.Stream;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class TestControllerTest {
@Nested
public class NestedTestControllerTest {
@ParameterizedTest
@MethodSource("provideKeyAndValue")
public void test(Integer key, String value) {
System.out.println(key + " " + value);
}
// 에러 발생!
private static Stream<Arguments> provideKeyAndValue() {
return Stream.of(
Arguments.of(1, "value1"),
Arguments.of(2, "value2"),
Arguments.of(3, "value3")
);
}
}
}
이렇게 불가능하다는 뜻이다... 이럴 때는 @TestInstance
를 사용하면 되는데, 이제부터 @TestInstance
에 대해 알아볼 예정이다.
@TestInstance는 테스트 인스턴스의 라이프 사이클을 설정할 때 사용한다.
import java.util.stream.Stream;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class TestControllerTest {
@Nested
@TestInstance(Lifecycle.PER_CLASS)
public class NestedTestControllerTest {
@ParameterizedTest
@MethodSource("provideKeyAndValue")
public void test(Integer key, String value) {
System.out.println(key + " " + value);
}
private Stream<Arguments> provideKeyAndValue() {
return Stream.of(
Arguments.of(1, "value1"),
Arguments.of(2, "value2"),
Arguments.of(3, "value3")
);
}
}
}
이렇게 @TestInstance
를 Lifecycle.PER_CLASS로 설정해주면 동일한 test instance에서 모든 test method를 실행할 수 있게 되며 provideKeyAndValue()를 static으로 두지 않고도 @MethodSource를 사용할 수 있게 된다.
사실 위와 같이 Unit Test를 작성해도 괜찮을지는 모르겠지만, 한 번도 접해보지 못한 어노테이션을 사용하였고 알게 되었다는 것에 의의를 둘 수 있었다.