티스토리 뷰
JUnit5 란?
JUnit이란 자바 프로그래밍 언어용 단위 테스트 프레임워크입니다.
SpringBoot 2.2.0 이전에는 JUnit4가 기본으로 설정되었지만, SpringBoot 2.2.0 버전부터는 JUnit5가 기본으로 설정됩니다.
JUnit5는 런타임 시 Java8 이상이 필요하며, Junit5를 사용하려면 Gradle 4.7 이상이 여야 합니다.
JUnit의 경우 Spring boot initializer에서 Spring-Web을 dependencies를 사용하게 되면 자동적으로 추가가 됩니다.
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform : 테스트를 발견하고 테스트 계획을 생성하는 Test Engine API를 가지고 있습니다. Platform은 TestEngine을 통해서 테스트를 발견하고 실행하고 결과를 보고합니다.
- JUnit Jupiter : Test Engine의 실제 구현체는 별도 모듈이며, 모듈 중 하나가 jupiter-engine입니다. 이 모듈은 jupiter-api를 사용해서 작성한 테스트 코드를 발견하고 실행합니다. Jupiter API는 JUnit 5에 새롭게 추가된 테스트 코드용 API로서, 개발자는 Jupiter API를 사용해서 테스트 코드를 작성할 수 있습니다
- JUnit Vintage : JUnit 4 버전으로 작성한 테스트 코드를 실행할 때에는 vintage-engine 모듈을 사용합니다.
JUnit5 어노테이션 (Annotation) 알아보기
JUnit5 어노테이션 | 설명 |
@Test | 테스트 Method임을 선언 / 정적 테스트 |
@ParameterizedTest | 매개변수를 받는 테스트를 작성 |
@RepeatedTest | 반복되는 테스트를 작성 |
@TestFactory | @Test로 선언된 정적 테스트가 아닌 동적으로 테스트를 사용 |
@TestInstance | 테스트 클래스의 생명주기를 설정 |
@TestTemplate | 공급자에 의해 여러 번 호출될 수 있도록 설계된 테스트 케이스 템플릿임을 나타냄 |
@TestMethodOrder | 테스트 메소드 실행 순서를 구성에 사용 |
@DisplayName | 테스트 클래스 or 메소드의 사용자 정의 이름을 선언할 때 사용 |
@DisplayNameGeneration | 이름 생성기를 선언함. 예를 들어 '_'를 공백 문자로 치환해주는 생성기가 있음. ex ) new_test -> new test |
@BeforeEach | 모든 테스트 실행 전에 실행할 테스트에 사용 |
@AfterEach | 모든 테스트 실행 후에 실행한 테스트에 사용 |
@BeforeAll | 현재 클래스를 실행하기 전 제일 먼저 실행할 테스트 작성 / static으로 선언 |
@AfterAll | 현재 클래스 종료 후 해당 테스트를 실행 / static으로 선언 |
@Nested | 클래스를 정적이 아닌 중첩 테스트 클래스임을 나타냄 |
@Tag | 클래스 또는 메소드 레벨에서 태그를 선언할 때 사용 |
@Disabled | 이 클래스나 테스트를 사용하지 않음 |
@Timeout | 테스트 실행 시간을 선언 후 초과되면 실패하도록 설정 |
@ExtendWith | 확장을 선언적으로 등록할 때 사용 |
@RegisterExtension | 필드를 통해 프로그래밍 방식으로 확장을 등록할 때 사용 |
@TempDir | 필드 주입 또는 매개변수 주입을 통해 임시 디렉토리를 제공하는데 사용 |
JUnit5 Assertions 알아보기
JUnit5 Assertions | 설명 |
assertArrayEquals | 예상 배열과 실제 배열이 동일한지 확인 배열이 같지 않으면 마지막 인자로 들어간 메세지가 출력됨 char[] val1 = { 'J', 'u', 'p', 'i', 't', 'e', 'r' }; char[] val2 = "Jupiter".toCharArray(); assertArrayEquals(val1, val2, "Array is different."); |
assertEquals | 두 값을 비교하여 일치 여부 판단 int val1 = 3 * 1; int val2 = 3 * 5; assertEquals(val1, val2); |
assertTrue & assertFalse | JUnit4 버전과 동일하며 BooleanSupplier도 사용 가능 BooleanSupplier condition = () -> 3 > 5; assertFalse(val, "3 is not greater then 5"); |
assertNull & assertNotNull | JUnit4 버전과 동일하며 객체의 null 여부 확인 assertNull(val, () -> "should be null"); |
assertSame & assertNotSame | JUnit4 버전과 동일하며 예상되는 값과 실제 값이 동일한 객체를 참조하는지 확인 String val1 = "Java"; Optional<String> val2 = Optional.of(language); assertSame(val1, val2.get()); |
fail | 제공된 실패 메시지와 기본 원인으로 테스트에 실패 개발이 완료되지 않은 테스트를 표시하는 데 유용 fail("Fail Error"); |
assertAll | - 모든 Assertion이 실행되고 실패가 함께 보고되는 그룹화된 Assertion - MultipleFailureError에 대한 메시지 문자열에 포함될 제목과 실행 가능한 스트림을 허용 - 실행 파일 중 하나에서 OutOfMemoryError가 발생한 경우에만 중단됨 - 메소드 내에서 인자로 람다식을 사용 - 여러 개의 람다식이 동시에 실행됨 assertAll( "test", () -> assertEquals(15, 3 * 5, "15 is 3 times 5"), () -> assertEquals("java", "JAVA".toLowerCase()), () -> assertEquals(null, null, "null is equal to null") ); |
assertIterableEquals | - 예상 반복 가능 항목과 실제 반복 가능 항목이 동일한지 확인 - 두 Iterable은 동일한 순서로 동일한 요소를 반환해야 함 - 두 Iterable이 동일한 유형일 필요는 없음 - 아래에서 서로 다른 유형의 두 목록(LinkedList 및 ArrayList)이 동일한지 확인 Iterable<String> val1 = new ArrayList<>(asList("Java", "Junit", "Test")); Iterable<String> val2 = new LinkedList<>(asList("Java", "Junit", "Test")); assertIterableEquals(val1, val2); |
assertNotEquals | 예상 값과 실제 값이 다름을 확인 Integer val = 5; assertNotEquals(0, val, "The result cannot be 0"); |
assertLinesMatch | - 예상 목록이 실제 목록과 일치하는지 확인 - assertEquals, assertIterableEquals와 다름 > 예상 줄이 실제 줄과 같은지 확인 > 같으면 다음 쌍으로 이동 > String.matches() 메서드로 검사 > fast-forward marker 확인 - 아래에서 두 목록에 일치하는 행이 있는지 검사 List<String> val1 = asList("Java", "\\d+", "JUnit"); List<String> val2 = asList("Java", "11", "JUnit"); assertLinesMatch(expected, actual); |
assertThrows | - 특정 예외가 발생하였는지 확인 - 첫 번째 인자는 확인할 예외 클래스 - 두 번째 인자는 테스트하려는 코드 Throwable exception = assertThrows( IllegalArgumentException.class, () -> { throw new IllegalArgumentException("Exception message"); } ); assertEquals("Exception message", exception.getMessage()); |
assertTimeout & assertTimeoutPreemptively |
- 특정 시간 안에 실행이 끝나는지 확인 - 시간 내 실행이 끝나는지 여부 확인 시 : assertTimeout - 지정한 시간 내 끝나지 않으면 바로 종료 : assertTimeoutPreemptively assertTimeout( ofSeconds(2), () -> { // code that requires less then 2 minutes to execute Thread.sleep(1000); } ); |
Given-When-Then
Given-When-Then는 테스트 코드를 작성하는 반 구조화(semi-structured) 된 방법입니다.
- given은 테스트에서 구체화하고자 하는 행동을 시작하기 전에 테스트 상태를 설명하는 부분
- when은 구체화하고자 하는 그 행동
- then은 어떤 특정한 행동 때문에 발생할 거라고 예상되는 변화에 대해 설명하는 부분
SpringBoot Junit5 따라 하기
Junit5 사용을 위해 의존성 추가와 테스트 로직을 구현해보겠습니다.
Sources영역과 tests영역이 분리되어있으며, 구조는 동일하게 구성해 주는 게 좋습니다.
build.gradle
tasks.named('test') {
useJUnitPlatform()
}
testImplementation ('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' //Junit4제외
}
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2'
Repository Interface에서 "Ctrl+Shift + t"로 Test Class를 만들어 줍니다.
BoxOfficeRepositoryTest.java
package com.api.opendata.dao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.junit.jupiter.api.*;
import static org.assertj.core.api.Assertions.assertThat;
import com.api.opendata.model.boxoffice.MovieVO;
import java.util.List;
import java.util.stream.Collectors;
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class BoxOfficeRepositoryTest {
@Autowired
private BoxOfficeRepository boxOfficeRepository;
@BeforeAll
static void beforeAll() {
System.out.println("JUnit - @BeforeAll");
}
@AfterAll
static void afterAll() {
System.out.println("JUnit - @AfterAll");
}
@BeforeEach
void beforeEach() {
System.out.println("JUnit - @BeforeEach");
}
@AfterEach
void afterEach() {
System.out.println("JUnit - @AfterEach");
}
@Test
void findAllTest() {
//given
List<MovieVO> movieList = boxOfficeRepository.findAll();
//when
List<MovieVO> actionList = movieList.stream().filter(f -> "액션".equals(f.getRepGenreNm())).collect(Collectors.toList());
//then
assertThat(actionList)
.isNotEmpty()
.hasSize(2);
}
@Test
void test2() {
System.out.println("JUnit - @Test test2");
}
@Test
@Disabled
void disabled_test() {
System.out.println("JUnit - @Test @Disabled");
}
}
- Unit Test에 성공한 경우
- Unit Test에 실패한 경우
movieList에는 총 4Row의 데이터가 있으며, 장르가 액션인 Row는 2개입니다.
hasSize에서 2로 했을 경우 일치하므로 성공하였지만, 2가 아닌 다른 값인 경우 일치하지 않으므로 실패하였습니다.
참고
https://junit.org/junit5/docs/current/user-guide/#overview
https://steady-coding.tistory.com/349
'IT > Spring' 카테고리의 다른 글
[Spring] Spring이란 무엇인가? (0) | 2022.09.16 |
---|---|
[Spring] SpringBoot + MariaDB JPA (0) | 2022.08.26 |
[Spring] SpringBoot + MariaDB JDBC && MyBatis with Docker (0) | 2022.08.24 |
[Spring] SpringBoot + 공공데이터 with lombok & jackson (0) | 2022.08.23 |
[Spring] Spring Boot + Java SE 17 설정 with IntelliJ (0) | 2022.08.10 |