Junit 5 Eaxample
이 내용은 공식문서 https://junit.org/junit5/docs/current/user-guide/ 를 참고 하였습니다.
많은 내용은 다루지 않고 몇가지 기능만 다루어 보려고 합니다.
Junit5가 나온지는 꽤 되었지만 사용하고 있는 기능만 사용하고 새로운 기능들은 잘 모르는 상태였습니다.
인턴생활을 하면서 시간이 널널하기에 새로운 버전에 대해서 공부해볼려고 합니다.
Junit 5 란?
Junit의 이전 버전과 달리 Junit 5는 세가지 하위 프로젝트의 여러 모듈로 구성됩니다.
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
junit 플랫폼
- 테스트 프레임워크 실행은 JVM에서 동작합니다.
Junit Jupiter
- Junit 5에서 테스트를 하기위한 프로그래밍 모델 과 익스텐션 모델의 조합입니다.
- Jupiter 하위 프로젝트는 TestEngine 플랫폼에서 Jupiter 기반 테스트를 제공합니다.
Junit Vintage
- Junit 3 및 Junit 4 기반 테스트를 시행합니다.
지원 되는 Java
- java 8 이상
Junit Jupiter
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
다음 예제를 가지고 테스트 하겠습니다.
Caclculator.java
package com.example.junit5.project;
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
첫 번째 테스트
package com.example.junit5.project;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class MyFirstJUnitJupiterTests {
private final Calculator calculator = new Calculator();
@Test
void test() {
assertEquals(2, calculator.add(1, 1));
}
}
어노테이션
@Test | 메서드가 테스트 메서드임을 나타냅니다. JUnit 4의 @Test어노테이션 과 달리이 어노테이션은 JUnit Jupiter의 테스트 확장이 자체 전용 어노테이션을 기반으로 작동하기 때문에 속성을 선언하지 않습니다. 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 . |
@ParameterizedTest | 메서드가 매개 변수 가있는 테스트 임을 나타냅니다 . 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 . |
@RepeatedTest | 메서드가 반복 테스트를 위한 테스트 템플릿임을 나타냅니다 . 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 . |
@TestFactory | 메서드가 동적 테스트를 위한 테스트 팩토리임을 나타냅니다 . 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 . |
@TestTemplate | 메서드가 등록 된 공급자가 반환 한 호출 컨텍스트 수에 따라 여러 번 호출되도록 설계된 테스트 케이스 의 템플릿 임을 나타냅니다 . 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 . |
@TestMethodOrder | 주석이 달린 테스트 클래스에 대한 테스트 메서드 실행 순서 를 구성하는 데 사용됩니다 . JUnit 4의 @FixMethodOrder. 이러한 주석은 상속 됩니다. |
@TestInstance | 주석이 달린 테스트 클래스에 대한 테스트 인스턴스 수명주기 를 구성하는 데 사용됩니다 . 이러한 주석은 상속 됩니다. |
@DisplayName | 테스트 클래스 또는 테스트 메서드에 대한 사용자 지정 표시 이름 을 선언합니다 . 이러한 주석은 상속 되지 않습니다 . |
@DisplayNameGeneration | 테스트 클래스에 대한 사용자 지정 표시 이름 생성기 를 선언합니다 . 이러한 주석은 상속 됩니다. |
@BeforeEach | 의미 주석 메소드가 실행되어야 함 전에 각각 @Test , @RepeatedTest, @ParameterizedTest, 또는 @TestFactory현재의 메소드; JUnit 4의 @Before. 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 . |
@AfterEach | 의미 주석 메소드가 실행되어야 함 후에 각각 @Test , @RepeatedTest, @ParameterizedTest, 또는 @TestFactory현재의 메소드; JUnit 4의 @After. 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 . |
@BeforeAll | 주석 메소드가 실행되어야 함을 나타내고 전에 모든 @Test , @RepeatedTest, @ParameterizedTest및 @TestFactory현재 클래스의 메소드; JUnit 4의 @BeforeClass. 이러한 메서드는 상속되고 ( 숨겨 지거나 재정의 되지 않는 한 ) 반드시 상속 되어야합니다 static( "클래스 별" 테스트 인스턴스 수명주기 가 사용 되지 않는 경우 ). |
@AfterAll | 주석 메소드가 실행되어야 함을 나타내고, 이후 모든 @Test , @RepeatedTest, @ParameterizedTest및 @TestFactory현재 클래스의 메소드; JUnit 4의 @AfterClass. 이러한 메서드는 상속되고 ( 숨겨 지거나 재정의 되지 않는 한 ) 반드시 상속 되어야합니다 static( "클래스 별" 테스트 인스턴스 수명주기 가 사용 되지 않는 경우 ). |
@Nested | 주석이 달린 클래스가 정적이 아닌 중첩 테스트 클래스 임을 나타냅니다 . @BeforeAll및 @AfterAll방법은 직접 사용할 수 없습니다 @Nested은 "당 클래스"를 제외 테스트 클래스 테스트 인스턴스 라이프 사이클이 사용됩니다. 이러한 주석은 상속 되지 않습니다 . |
@Tag | 클래스 또는 메서드 수준에서 테스트 필터링을위한 태그 를 선언하는 데 사용됩니다 . TestNG의 테스트 그룹 또는 JUnit 4의 Categories와 유사합니다. 이러한 주석은 클래스 수준에서 상속 되지만 메서드 수준 에서는 상속 되지 않습니다. |
@Disabled | 테스트 클래스 또는 테스트 메서드 를 비활성화 하는 데 사용됩니다 . JUnit 4의 @Ignore. 이러한 주석은 상속 되지 않습니다 . |
@Timeout | 실행이 주어진 기간을 초과하는 경우 테스트, 테스트 팩토리, 테스트 템플릿 또는 수명주기 메서드를 실패하는 데 사용됩니다. 이러한 주석은 상속 됩니다. |
@ExtendWith | 확장을 선언적 으로 등록하는 데 사용됩니다 . 이러한 주석은 상속 됩니다. |
@RegisterExtension | 필드를 통해 프로그래밍 방식으로 확장 을 등록하는 데 사용됩니다 . 이러한 필드는 음영 처리 되지 않는 한 상속 됩니다 . |
@TempDir | 라이프 사이클 방법 또는 테스트 방법에서 필드 주입 또는 매개 변수 주입을 통해 임시 디렉토리 를 제공하는 데 사용됩니다 . 에있는 org.junit.jupiter.api.io패키지. |
어노테이션 커스톰
Junit Jupiter 주석은 메타 주석으로 사용할 수있습니다. (상속기능이 있습니다.)
어노테이션을 정의하고
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@Test
public @interface FastTest {
}
다음과 같이 사용할 수있습니다. @Tag("fast")를 붙여넣는 대신 사용할 수 있어 가독성이 올라간답니다..
@FastTest
void test() {
assertEquals(2, calculator.add(1, 1));
}
생명주기
Junit5에는 다음과 같은 생명주기가 존재합니다.
package com.example.junit5.project;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
public class StandardTests {
@BeforeAll
static void initAll() {
}
@BeforeEach
void init() {
}
@Test
void succeedingTest() {
}
@Test
void failingTest() {
fail("a failing test");
}
@Test
@Disabled("for demonstration purposes")
void skippedTest() {
// not executed
}
@Test
void abortedTest() {
assumeTrue("abc".contains("Z"));
fail("test should have been aborted");
}
@AfterEach
void tearDown() {
}
@AfterAll
static void tearDownAll() {
}
}
@DisplayNmae
다음 주석을 활용해서 테스터의 이름을 부여합니다.
package com.example.junit5.project;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class DisplayNameDemo {
@Test
@DisplayName("Custom test name containing spaces")
void testWithDisplayNameContainingSpaces() {
}
@Test
@DisplayName("╯°□°)╯")
void testWithDisplayNameContainingSpecialCharacters() {
}
@Test
@DisplayName("😱")
void testWithDisplayNameContainingEmoji() {
}
}
@DisplayNameGeneration
주석을 통해서 사용자 지정으로 이름을 생성합니다.
Standard | JUnit Jupiter 5.0이 출시 된 이후 표준 표시 이름 생성 동작과 일치합니다. |
Simple | 매개 변수가없는 메소드의 후행 괄호를 제거합니다. |
ReplaceUnderscores | 밑줄을 공백으로 바꿉니다. |
IndicativeSentences | 테스트 이름과 포함하는 클래스를 연결하여 완전한 문장을 생성합니다. |
IndicativeSentences
@IndicativeSentencesGeneration를 사용하여 사용자 지정이 가능합니다
package com.example.junit5.project;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.IndicativeSentencesGeneration;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class DisplayNameGeneratorDemo {
@Nested
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class A_year_is_not_supported {
@Test
void if_it_is_zero() {
}
@DisplayName("A negative value for year is not supported by the leap year computation.")
@ParameterizedTest(name = "For example, year {0} is not supported.")
@ValueSource(ints = { -1, -4 })
void if_it_is_negative(int year) {
}
}
@Nested
@IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class)
class A_year_is_a_leap_year {
@Test
void if_it_is_divisible_by_4_but_not_by_100() {
}
@ParameterizedTest(name = "Year {0} is a leap year.")
@ValueSource(ints = { 2016, 2020, 2048 })
void if_it_is_one_of_the_following_years(int year) {
}
}
}
운영 체제 조건
운영체제에 대한 테스트를 진행할 수 있습니다.
@Test
@EnabledOnOs(MAC)
void onlyOnMacOs() {
// ...
}
@TestOnMac
void testOnMac() {
// ...
}
@Test
@EnabledOnOs({ LINUX, MAC })
void onLinuxOrMac() {
// ...
}
@Test
@DisabledOnOs(WINDOWS)
void notOnWindows() {
// ...
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@EnabledOnOs(MAC)
@interface TestOnMac {
}
런타임 환경 조건
Jdk 버전에 따른 테스트를 진행 할 수있습니다.
@Test
@EnabledOnJre(JAVA_8)
void onlyOnJava8() {
// ...
}
@Test
@EnabledOnJre({ JAVA_9, JAVA_10 })
void onJava9Or10() {
// ...
}
@Test
@EnabledForJreRange(min = JAVA_9, max = JAVA_11)
void fromJava9to11() {
// ...
}
@Test
@EnabledForJreRange(min = JAVA_9)
void fromJava9toCurrentJavaFeatureNumber() {
// ...
}
@Test
@EnabledForJreRange(max = JAVA_11)
void fromJava8To11() {
// ...
}
@Test
@DisabledOnJre(JAVA_9)
void notOnJava9() {
// ...
}
@Test
@DisabledForJreRange(min = JAVA_9, max = JAVA_11)
void notFromJava9to11() {
// ...
}
@Test
@DisabledForJreRange(min = JAVA_9)
void notFromJava9toCurrentJavaFeatureNumber() {
// ...
}
@Test
@DisabledForJreRange(max = JAVA_11)
void notFromJava8to11() {
// ...
}
@Order 기능
우선순위를 부여할 수 있습니다 숫자가 작을 수록 우선순위가 큰 테스트 케이스 입니다.
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {
@Test
@Order(3)
void nullValues() {
// perform assertions against null values
}
@Test
@Order(2)
void emptyValues() {
// perform assertions against empty values
}
@Test
@Order(1)
void validValues() {
// perform assertions against valid values
}
}
반복 테스트
주석을 이용해 하나의 테스트케이스를 여러번 실행 할 수 있습니다.
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {
@Test
@Order(3)
void nullValues() {
// perform assertions against null values
}
@Test
@Order(2)
void emptyValues() {
// perform assertions against empty values
}
@Test
@Order(1)
void validValues() {
// perform assertions against valid values
}
}
Parameterized Tests
@ValueSource 어노테이션을 이용해서 forEach처럼 사용할 수 있습니다.
@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
assertTrue(StringUtils.isPalindrome(candidate));
}
@Timeout
비동기 코드를 처리 할 때 어설 션을 수행하기 전에 무기한 기다리는 것을 방지 할 수 있습니다.
@Test
@Timeout(5) // Poll at most 5 seconds
void pollUntil() throws InterruptedException {
while (asynchronousResultNotAvailable()) {
Thread.sleep(250); // custom poll interval
}
// Obtain the asynchronous result and perform assertions
}
병렬 실행
Junit 주피터는 기본적으로는 싱글 스레드로 동작합니다. 병렬 실행을 활성화 할려면 다음과 같이 설정파일에서 설정합니다. (다음은 모든테스트에서 병렬로 설정하는 기능입니다.)
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
'TOPIC' 카테고리의 다른 글
mac 구매후 설치방법(개발자용) (0) | 2021.06.08 |
---|---|
JOIN 안에 SUB쿼리를 JOIN으로 바꿔보자 (1) | 2021.01.17 |
JAVA : 배열 회전, 배열 90도 회전 방법 (1) | 2021.01.10 |
Access Token 클라이언트 보안전략 (0) | 2020.09.01 |
Spring boot image를 등록하고 이미지를 불러오는 방법 (3) | 2020.07.29 |