이 포스트는 TDD를 적용하기위해 필요한 단위테스팅의 정의와 JUnit5 어노테이션들을 소개하고 어노테이션 사용방법을 정리하기위해 작성 되었습니다.
JUnit은 자바 테스트 프레임워크다.
JUnit 등장 이전에 main에서 테스트를 했었고 많은 문제가 있었다.
main의 문제점
이러한 문제점을 보안 하기 위해 등장했다.
테스트 메소드 Annotation
@Test : 테스트 메소드를 선언하기 위해 사용한다.
@ParameterizedTest :@Test
와 달리 @ParameterizedTest
어노테이션은 테스트 메서드가 인자를 받을 수 있도록 한다.
@DisplayName : 테스트 클래스나 테스트 메소드에 이름을 붙여줄 때 사용한다
@Nested : test 클래스안에 Nested 테스트 클래스를 작성할 때 사용되며, static이 아닌 중첩클래스, 즉 Inner 클래스여야만 한다. 테스트 인스턴스 라이플사이클이 per-class 로 설정되어 있지 않다면 @BeforeAll
, @AfterAll
가 동작안하니 주의하자.
@Tag : 테스트를 필터링할 때 사용한다. 클래스또는 메소드레벨에 사용한다.
@Disabled : 테스트 클래스나, 메소드의 테스트를 비활성화 한다.
@Timeout : 주어진 시간안에 테스트가 끝나지 않으면 실패한다.
@ExtendWith : extension을 등록한다. 이 어노테이션은 상속이 된다. 확장팩이라고 생각하면 될 것 같다.
@RegisterExtension : 필드를 통해 extension을 등록한다. 이런 필드는 private이 아니라면 상속된다.
@TempDir : 필드 주입이나 파라미터 주입을 통해 임시적인 디렉토리를 제공할 때 사용한다.
라이프 사이클 메소드 Annotation
라이프 사이클 메소드란 테스트 메소드가 실행되기 전 OR 후 에 실행되는 메소드로 보통 공통적인 세팅을 위해 사용한다.
@BeforeEach : 각각 테스트 메소드가 실행되기전에 실행되어야 하는 메소드를 명시해준다.
@Test
, @RepeatedTest
, @ParameterizedTest
, @TestFactory
가 붙은 테스트 메소드가 실행하기 전에 실행된다.
@BeforeAll : @BeforeEach
는 각 테스트 메소드 마다 실행되지만, 이 어노테이션은 테스트가 시작하기 전 딱 한 번만 실행 된다.
@AfterEach : @Test
, @RepeatedTest
, @ParameterizedTest
, @TestFactory
가 붙은 테스트 메소드가 실행되고 난 후 실행된다.
검증을 위해 HashSet을 생성하고 HashSet에 대해 검증하겠습니다.
numbers
라는HashSet
을 생성하고 정수를add()
했습니다.
따라서 현재numbers
의size()
는 3입니다.
@Test
void setSizeTest() {
numbers = new HashSet<>();
numbers.add(1);
numbers.add(1);
numbers.add(3);
numbers.add(2);
}
Assertions의 종류는 아래 2가지가 있으며
- import org.assertj.core.api.Assertions;
- import org.junit.jupiter.api.Assertions;
가독성을 위해 위에 있는 core.api.Assertions를 사용하기를 권장합니다.
@Test
void setSizeTest() {
numbers = new HashSet<>();
numbers.add(1);
numbers.add(1);
numbers.add(3);
numbers.add(2);
//검증 코드 추가 numbers.size()가 3과 동일하면 true를 반환합니다.
Assertions.assertThat(numbers.size()).isEqualTo(3);
}
@ParameterizedTest
메서드는 인자를 획득하기 위해 @ValueSource
등의 어노테이션을 추가로 요구한다.
아래는 그 예시이다(없는건 아직 사용을 해보지 않아서 추가하지 않았다).
ValueSource는 원시 자료형의 배열을 메서드에 전달하고 전달된 배열의 크기만큼 테스트를 반복하여 실행합니다.
(파라미터로 배열의 원소가 하나씩 들어온다)
아래👇 코드는 3회 실행되고 number에 1,2,3이 한번씩 검증됩니다.
// @ValueSource는 문자열, 정수 등, 원시 자료형의 배열을 메서드에 전달할 수 있다.
@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void setContainTest(int number) {
numbers = new HashSet<>();
numbers.add(1);
numbers.add(1);
numbers.add(3);
numbers.add(2);
assertThat(numbers.contains(number)).isTrue();
}
아래는👇 int
말고 String
을 사용한 예제입니다.
단순히 argument Null
이 아닌지만 검증합니다.
@ParameterizedTest
@ValueSource(strings = { "Hello", "World" })
void testWithStringParameter(String argument) {
assertNotNull(argument);
}
@CsvSource
는 한번에 여러 값들을 배열로 넘길때 사용합니다.
기본적으로 쉼표가 열 구분자이고
delimiter 속성을 사용하여 사용자가 정의 할 수 있습니다 .
@ParameterizedTest
@CsvSource({"test,TEST", "tEst,TEST", "Java,JAVA"})
void toUpperCaseValue(String input, String expected) {
String actualValue = input.toUpperCase();
assertThat(actualValue).isEqualTo(expected);
}
@ParameterizedTest
@CsvSource({"test:TEST", "tEst:TEST", "Java:JAVA"}, delimiter = ':')
void toUpperCaseValue(String input, String expected) {
String actualValue = input.toUpperCase();
assertThat(actualValue).isEqualTo(expected);
@MethodSource
는 복잡한 argument를 전달 할 때 사용합니다.
스트림 형태가 대부분이지만 리스트와 같은 다른 컬렉션도 가능합니다.
아래👇 코드에서 스트림형태로 String
과 boolean
이 argument로 넘어오도록 provideStringsForIsBlank()
메서드를 정의하고 메서드를 @MethodSource
의 이름으로 입력합니다.
만일 이름이 입력되지 않는다면 테스트 메소드의 이름인 methodSourceTest
라는 메소드를 클래스 내부에서 찾게 됩니다.
@ParameterizedTest
@MethodSource("provideStringsForIsBlank")
void methodSourceTest(String input, boolean expected) {
assertEquals(expected, Strings.isBlank(input));
}
private static Stream<Arguments> provideStringsForIsBlank() {
return Stream.of(
Arguments.of(null, true),
Arguments.of("", true),
Arguments.of(" ", true),
Arguments.of("not blank", false)
);
}
@NullSource
는 파라미터로 단순히 Null값을 넘겨줍니다.
@ParameterizedTest
@NullSource
void isBlank_ShouldReturnTrueForNullInputs(String input) {
assertTrue(Strings.isBlank(input));
}
HashSet을 검증하는 위 코드에서 중복되는 코드가 있습니다.
바로 아래와 같은 HashSet을 선언하고 초기화시키는 코드입니다.
@Test
void setSizeTest() {
numbers = new HashSet<>();
numbers.add(1);
numbers.add(1);
numbers.add(3);
numbers.add(2);
//검증 코드 추가 numbers.size()가 3과 동일하면 true를 반환합니다.
Assertions.assertThat(numbers.size()).isEqualTo(3);
}
이런 상황에서 모든 테스트 메서드 실행 전 호출 되는
@BeforeEach
를 적용 하면 코드를 간결하게 만들수 있습니다.
적용시 아래와 같이 코드가 간결해 집니다.
@BeforeEach
void setUp() {
numbers = new HashSet<>();
numbers.add(1);
numbers.add(1);
numbers.add(3);
numbers.add(2);
}
@Test
void setSizeTest() {
assertThat(numbers.size()).isEqualTo(3);
}
@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void setContainTest(int number) {
assertThat(numbers.contains(number)).isTrue();
}