Junit은 자바에서 사용하는 유닛 테스트 프레임워크이다. 요즘 자바로 TDD(테스트 주도 개발)를 연습하고 있는데 공부할 겸 Junit5 어노테이션을 정리하고자 한다.
Junit은 어노테이션으로 다양한 기능을 제공한다.
@Test
테스트 메서드임을 나타낸다.
@Test
void 테스트() {
//...
}
@ParameterizedTest
매개변수를 받아서 여러 입력에 대한 결과를 한 번에 테스트할 수 있다.
@ParameterizedTest
@ValueSource(strings = {"dog", "cat"})
void 매개변수_테스트(String input) {
// ...
}
여러 입력에 다른 결과를 테스트하고 싶을 때 사용하는 방법이다.
@ParameterizedTest
@CsvSource(value = {"6:FIRST", "5:THIRD", "4:FOURTH", "3:FIFTH", "2:NOTHING"}, delimiter = ':')
void findRankByMatchingCountAndBonus_ExceptSecond(String input, String expected) {
// 번호 몇 개가 맞는지에 따라 로또 몇 등인지 반환
assertThat(LottoRank.valueOf(Integer.parseInt(input), false))
.isEqualTo(LottoRank.valueOf(expected));
}
@RepeatedTest
같은 메서드에 여러 번 테스트를 돌릴 때 쓸 수 있다. 밑의 테스트는 5번 반복한다.
@RepeatedTest(5)
void 반복_테스트() {
// ...
}
@TestFactory
@Test와 달리 그 자체가 테스트 케이스가 아닌 여러 테스트 케이스를 실행시킬 수 있는 테스트 공장이다. 동적 테스트를 할 때 사용된다. 밑의 예시는 숫자가 짝수인지를 알려주는 isEvenNumber()를 테스트하는 @TestFactory의 사용 예이다.
@TestFactory
Stream<DynamicTest> isEvenNumberTest() {
List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10, 12, 14, 16);
return numbers.stream()
.map(num -> dynamicTest(num + "은 짝수",
() -> {
boolean result = isEvenNumber(num);
assertThat(result).isTrue();
}));
}
dynamicTest()는 DynamicTest 타입을 반환하는 생성 메서드로 예시와 같이 DynamicTest를 인스턴스로 가지는 Stream을 반환하면 각 숫자에 대해 테스트를 진행한다. Stream이 아니라 Collection이나 Iterator를 반환해도 되는데 자세한 내용은 링크 참조하면 이해할 수 있을 것이다. 위 테스트를 실행하면 결과는 다음과 같다.
@TestClassOrder, @TestMethodOrder
원래 테스트는 순서에 의존하지 않도록 짜야하고 실행 순서도 순서대로 실행되지 않는다. 하지만 순서대로 테스트를 진행하고 싶을 때 사용할 수 있는 어노테이션이다.
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class TestClassOrderTest {
@Nested
@Order(1)
class FirstTests {
@Test
void test1() {
}
}
@Nested
@Order(2)
class SecondTests {
@Test
void test2() {
}
}
}
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MethodOrderTest {
@Test
@Order(1)
void test1() {
}
@Test
@Order(2)
void test02() {
}
@Test
@Order(3)
void test03() {
}
}
@TestInstance
Junit은 테스트 메서드마다 인스턴스를 만들기 때문에 원래 밑의 예시는 test1()만 통과되어야 한다. 하지만 @TestInstance의 라이프 사이클을 PER_CLASS로 설정해주면 number가 해당 클래스의 모든 테스트 메서드에게 공유되어 test1()을 거친 뒤 test2()가 실행되면 둘 다 통과될 것이다.
@TestInstance(value = TestInstance.Lifecycle.PER_CLASS)
class TestInstanceTest {
private int number = 1;
@Test
void test1() {
assertThat(++number).isEqualTo(2);
}
@Test
void test2() {
assertThat(number).isEqualTo(2);
}
}
@DisplayName
테스트 메서드 명을 지정할 수 있다.
@Test
@DisplayName("DisplayName 테스트")
void test() {
//...
}
@AfterEach, @BeforeEach, @AfterAll, @BeforeAll
~Each는 각각의 모든 테스트 메서드를 실행하기 전과 후에 실행되어야 하는 메서드에 사용한다.
~All은 클래스에 존재하는 모든 테스트 메서드들이 실행되기 전과 후에 한 번씩만 실행되어야 하는 메서드에 사용한다.
@BeforeEach
void methodInit(){
// ...
}
@AfterEach
void methodClear() {
// ...
}
@BeforeAll
void classInit(){
// ...
}
@AfterAll
void classClear() {
// ...
}
@Nested
비슷해서 그룹화 시켜줄 필요가 있는 테스트들을 내부 클래스로 묶어 테스트할 수 있게 해 준다. 테스트를 계층적으로 관리할 수도 있고 가독성도 증가한다. 가위바위보 기능을 테스트한다고 가정해보자.
@DisplayName("가위바위보에서")
public class NestedTest {
@Nested
@DisplayName("상대가 묵을 내면")
class Rock {
@Test
@DisplayName("보를 내면 이긴다.")
void test1() {
}
@Test
@DisplayName("묵을 내면 비긴다.")
void test2() {
}
@Test
@DisplayName("찌를 내면 진다.")
void test3() {
}
}
@Nested
@DisplayName("상대가 찌을 내면")
class Scissors {
@Test
@DisplayName("보를 내면 진다.")
void test1() {
}
@Test
@DisplayName("묵을 내면 이긴다.")
void test2() {
}
@Test
@DisplayName("찌를 내면 비긴다.")
void test3() {
}
}
@Nested
@DisplayName("상대가 보를 내면")
class Paper {
@Test
@DisplayName("보를 내면 비긴다.")
void test1() {
}
@Test
@DisplayName("묵을 내면 진다.")
void test2() {
}
@Test
@DisplayName("찌를 내면 이긴다.")
void test3() {
}
}
}
실행하면 결과는 다음과 같다.
@Disabled
테스트를 한 번에 돌렸을 때 실행하지 않을 테스트 메서드에 사용한다.
@Disabled
@Test
void disabledTest() {
// ...
}