junit5가 작년 9월에 정식 릴리즈 되었으며 기존과 많은 부분이 달라져 정리.
- 개발자 친화적인 테스팅 프레임웤의 귀환
- 10여년 만의 major 버전 업데이트
- JUnit4: 2005년(4.12 : 2014년)
- JUnit5: 2017년(9월 릴리즈, bug fix : 11월)
- java8 or later support
junit5가 2017년 9월에 정식 릴리즈 되었으며 기존과 많은 부분이 달라져 정리.
- 개발자 친화적인 테스팅 프레임웤의 귀환
- 10여년 만의 major 버전 업데이트
- JUnit4: 2005년(4.12 : 2014년)
- JUnit5: 2017년(9월 릴리즈, bug fix : 11월)
- java8 or later support
JUnit5란?
- JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform
- JVM 기반 테스트 프레임워크를 실행하기 위한 기반 모듈.
- 플랫폼을 기반으로 실행 가능한 모든 테스트 진행을 위한 테스트 엔진을 제공하고 `Console Launcher` 를 통해 Gradle, Maven 과 연동 가능하게 제공.
- 기존 JUnit4 이하 환경에서의 테스트를 위해 Runner를 제공.
- Junit Jupiter
- JUnit 5 기반의 테스트를 작성하기 위한 모듈.
- Junit Vintage
- JUnit4 이하의 테스트를 실행시키기 위한 모듈.
JUnit5는 실행 시에 Java8 이상이 필요하지만 compile 은 가능하다.
- Installation : 의존성 정보
- Junit Platform
- Group ID: : org.junit.platform
Artifact IDDescriptionjunit-platform-commonsJUnit 자체적으로 사용하고 있는 공통 라이브러리.junit-platform-consoleConsole을 통해 수행되는 테스트 지원.junit-platform-console-standaloneConsole Launcher를 통한 Jar 파일 실행 지원.junit-platform-engineJUnit 5 기반의 테스트 지원.junit-platform-gradle-pluginGradle을 통한 테스트 지원.junit-platform-launcherIDE에서 테스트 플랜 구성 및 실행하는데 사용.junit-platform-runnerJUnit4 기반의 테스트 지원.junit-platform-suite-apiTest suite 구성을 위한 annotation 지원.junit-platform-surefire-providerMaven surfire 지원.- JUnit Jupiter
- Group ID: org.junit.jupiter
Artifact IDDescriptionjunit-jupiter-api테스트 케이스 작성 및 확장을 위한 APIjunit-jupiter-engineJupiter 모듈의 TestEnginejunit-jupiter-paramsParameterized 테스트 지원을 위한 라이브러리junit-jupiter-migrationsupportJUnit4에서의 마이그레이션 지원.- JUnit Jupiter
- Group ID: org.junit.vintage
Artifact IDDescriptionjunit-vintage-engineJUnit4 이하에서 작성된 테스트 케이스를 실행시킬때 사용되는 테스트 엔진.
Annotation
- JUnit Jupiter에서 테스트 확장을 위해 다양한 annotation을 제공.
- junit-jupiter-api 모듈의 `org.junit.jupiter.api` 패키지에 정의됨.
- annotationdesctiption상속 여부JUnit4변경 사항.@Test테스트 메소드임을 표시함. 상속 됨.override 되지 않으면@Test추가 인자를 사용하지 않음.(ex. expected)@ParameterizedTest인자를 제공받아 진행되는 테스트임을 나타냄.override 되지 않으면신규@RepeatedTest반복 테스트를 위한 테스트 템플릿.override 되지 않으면신규@TestFactory동적 테스트 수행을 위한 TestFactory임을 나타냄.override 되지 않으면신규@TestInstanceTest class 의 생명주기를 설정하기 위한 설정.상속됨신규@TestTemplateProvider에 의해 여러번 호출될수 있도록 설계됨. Stream에서 여러번 호출할 수 있음.override 되지 않으면신규@DisplayName테스트 클래스나 메소드를 원하는 이름으로 표시함.상속 안됨신규@BeforeEach해당 클래스에서 모든 테스트 메소드(@Test, @RepeatedTest, @ParameterizedTest, or @TestFactory) 실행 전 수행.override 되지 않으면@Before@AfterEach해당 클래스에서 모든 테스트 메소드(@Test, @RepeatedTest, @ParameterizedTest, or @TestFactory) 실행 후 수행.override 되지 않으면@After@BeforeAll해당 클래스에서 최초 실행되는 메소드. 클래스 단위의 생명주기가 별도 설정되지 않으면 static method이어야 한다.hidden이 아니거나 override되지 않은 경우@BeforeClass@AfterAll해당 클래스에서 최후에 실행되는 메소드. 클래스 단위의 생명주기가 별도 설정되지 않으면 static method이어야 한다.hidden이 아니거나 override되지 않은 경우@AfterClass@Nested해당 클래스가 nested, non-static class 임을 표시. `per-class` 생명주기가 사용되지 않는다면 @Nested선언된 클래스에서는 @BeforeAll, @AfterAll method는 사용될수 없음.상속 안됨신규@Tag클래스나 메소드 레벨로 test들을 필터링 하기 위한 annotation.클래스단위 적용만 상속TestNG or Categories@Disabled일반적인 테스트 수행 시 진행되지 않도록 함.상속 안됨@Ignore@ExtendWithcustom 확장을 등록할 때 사용.상속됨
- Annotation 조합
- custom annotation을 통해 annotation 조합이 가능함.
- @Tag("fast") → @Fast 를 사용할 수 있음.
Standard Test 작성
- @Test 는 가장 기본적인 annotation. junit4 시절과 달리 옵션이 없음.
- import static org.junit.jupiter.api.Assertions.fail;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;class StandardTests {@BeforeAllstatic void initAll() {}@BeforeEachvoid init() {}@Testvoid succeedingTest() {}@Testvoid failingTest() {fail("a failing test");}@Test@Disabled("for demonstration purposes")void skippedTest() {// not executed}@AfterEachvoid tearDown() {}@AfterAllstatic void tearDownAll() {}}
Display Names
- custom display name - with spaces, special characters, and evne emojis
- displayed by test runners and test reporting.
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @DisplayName("A special test case") class DisplayNameDemo { @Test @DisplayName("Custom test name containing spaces") void testWithDisplayNameContainingSpaces() { } @Test @DisplayName("╯°□°)╯") void testWithDisplayNameContainingSpecialCharacters() { } @Test @DisplayName("😱") void testWithDisplayNameContainingEmoji() { } } |
Assertions
- custom display name - with spaces, special characters, and evne emojis
- displayed by test runners and test reporting.
import static java.time.Duration.ofMillis; import static java.time.Duration.ofMinutes; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeout; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; class AssertionsDemo { @Test void standardAssertions() { assertEquals(2, 2); assertEquals(4, 4, "The optional assertion message is now the last parameter."); assertTrue(2 == 2, () -> "Assertion messages can be lazily evaluated -- " + "to avoid constructing complex messages unnecessarily."); } @Test void groupedAssertions() { // In a grouped assertion all assertions are executed, and any // failures will be reported together. assertAll("person", () -> assertEquals("John", person.getFirstName()), () -> assertEquals("Doe", person.getLastName()) ); } @Test void dependentAssertions() { // Within a code block, if an assertion fails the // subsequent code in the same block will be skipped. assertAll("properties", () -> { String firstName = person.getFirstName(); assertNotNull(firstName); // Executed only if the previous assertion is valid. assertAll("first name", () -> assertTrue(firstName.startsWith("J")), () -> assertTrue(firstName.endsWith("n")) ); }, () -> { // Grouped assertion, so processed independently // of results of first name assertions. String lastName = person.getLastName(); assertNotNull(lastName); // Executed only if the previous assertion is valid. assertAll("last name", () -> assertTrue(lastName.startsWith("D")), () -> assertTrue(lastName.endsWith("e")) ); } ); } @Test void exceptionTesting() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("a message"); }); assertEquals("a message", exception.getMessage()); } @Test void timeoutNotExceeded() { // The following assertion succeeds. assertTimeout(ofMinutes(2), () -> { // Perform task that takes less than 2 minutes. }); } @Test void timeoutNotExceededWithResult() { // The following assertion succeeds, and returns the supplied object. String actualResult = assertTimeout(ofMinutes(2), () -> { return "a result"; }); assertEquals("a result", actualResult); } @Test void timeoutNotExceededWithMethod() { // The following assertion invokes a method reference and returns an object. String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting); assertEquals("hello world!", actualGreeting); } @Test void timeoutExceeded() { // The following assertion fails with an error message similar to: // execution exceeded timeout of 10 ms by 91 ms assertTimeout(ofMillis(10), () -> { // Simulate task that takes more than 10 ms. Thread.sleep(100); }); } @Test void timeoutExceededWithPreemptiveTermination() { // The following assertion fails with an error message similar to: // execution timed out after 10 ms assertTimeoutPreemptively(ofMillis(10), () -> { // Simulate task that takes more than 10 ms. Thread.sleep(100); }); } private static String greeting() { return "hello world!"; } } |
Assumptions
- custom display name - with spaces, special characters, and evne emojis
- displayed by test runners and test reporting.
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.Assumptions.assumingThat; import org.junit.jupiter.api.Test; class AssumptionsDemo { @Test void testOnlyOnCiServer() { assumeTrue("CI".equals(System.getenv("ENV"))); // remainder of test } @Test void testOnlyOnDeveloperWorkstation() { assumeTrue("DEV".equals(System.getenv("ENV")), () -> "Aborting test: not on developer workstation"); // remainder of test } @Test void testInAllEnvironments() { assumingThat("CI".equals(System.getenv("ENV")), () -> { // perform these assertions only on the CI server assertEquals(2, 2); }); // perform these assertions in all environments assertEquals("a string", "a string"); } } |
Disabling Tests
- Class 단위의 disabling.
- method 단위의 disabling.
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @Disabled class DisabledClassDemo { @Test void testWillBeSkipped() { } } |
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class DisabledTestsDemo { @Disabled @Test void testWillBeSkipped() { } @Test void testWillBeExecuted() { } } |
Tagging & Filtering
- Tag 제약
- tag는 null이거나 공백이면 안된다.
- 트림된 tag는 공백을 포함해서는 안된다.
- 트림된 tag는 ISO control character들을 포함할 수 없다.
- 트림된 tag는 아래 예약어를 포함할 수 없다.(`,`, `(`, `)`, `&`, `|`, `!`)
- 공백을 포함해서는 안된다.
import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag("fast") @Tag("model") class TaggingDemo { @Test @Tag("taxes") void testingTaxCalculation() { } } |
Test Instance Lifecycle
- test instance lifecycle
- default는 LifeCycle.PER_METHOD.
- class 내에서 동일한 test instance들을 사용할 때 LifeCycle.PER_CLASS로 변경할 수 있음.
- @BeforeAll, @AfterAll 에 non-static method를 사용할 수 있고 @Nested 안에서도 사용가능하게 해준다.
- 트림된 tag는 아래 예약어를 포함할 수 없다.(`,`, `(`, `)`, `&`, `|`, `!`)
- 공백을 포함해서는 안된다.
-Djunit.jupiter.testinstance.lifecycle.default=per_class junit-platform.properties(src/test/resources) junit.jupiter.testinstance.lifecycle.default = per_class |
Nested Tests
- test instance lifecycle
- default는 LifeCycle.PER_METHOD.
- class 내에서 동일한 test instance들을 사용할 때 LifeCycle.PER_CLASS로 변경할 수 있음.
- @BeforeAll, @AfterAll 에 non-static method를 사용할 수 있고 @Nested 안에서도 사용가능하게 해준다.
- 트림된 tag는 아래 예약어를 포함할 수 없다.(`,`, `(`, `)`, `&`, `|`, `!`)
- 공백을 포함해서는 안된다.
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.EmptyStackException; import java.util.Stack; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @DisplayName("A stack") class TestingAStackDemo { Stack<Object> stack; @Test @DisplayName("is instantiated with new Stack()") void isInstantiatedWithNew() { new Stack<>(); } @Nested @DisplayName("when new") class WhenNew { @BeforeEach void createNewStack() { stack = new Stack<>(); } @Test @DisplayName("is empty") void isEmpty() { assertTrue(stack.isEmpty()); } @Test @DisplayName("throws EmptyStackException when popped") void throwsExceptionWhenPopped() { assertThrows(EmptyStackException.class, () -> stack.pop()); } @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked() { assertThrows(EmptyStackException.class, () -> stack.peek()); } @Nested @DisplayName("after pushing an element") class AfterPushing { String anElement = "an element"; @BeforeEach void pushAnElement() { stack.push(anElement); } @Test @DisplayName("it is no longer empty") void isNotEmpty() { assertFalse(stack.isEmpty()); } @Test @DisplayName("returns the element when popped and is empty") void returnElementWhenPopped() { assertEquals(anElement, stack.pop()); assertTrue(stack.isEmpty()); } @Test @DisplayName("returns the element when peeked but remains not empty") void returnElementWhenPeeked() { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); } } } } |
Dependency Injection for Constructors and Methods
- test instance lifecycle
- default는 LifeCycle.PER_METHOD.
- class 내에서 동일한 test instance들을 사용할 때 LifeCycle.PER_CLASS로 변경할 수 있음.
- @BeforeAll, @AfterAll 에 non-static method를 사용할 수 있고 @Nested 안에서도 사용가능하게 해준다.
- 트림된 tag는 아래 예약어를 포함할 수 없다.(`,`, `(`, `)`, `&`, `|`, `!`)
- 공백을 포함해서는 안된다.
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; @DisplayName("TestInfo Demo") class TestInfoDemo { TestInfoDemo(TestInfo testInfo) { assertEquals("TestInfo Demo", testInfo.getDisplayName()); } @BeforeEach void init(TestInfo testInfo) { String displayName = testInfo.getDisplayName(); assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()")); } @Test @DisplayName("TEST 1") @Tag("my-tag") void test1(TestInfo testInfo) { assertEquals("TEST 1", testInfo.getDisplayName()); assertTrue(testInfo.getTags().contains("my-tag")); } @Test void test2() { } } |
import java.util.HashMap; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; class TestReporterDemo { @Test void reportSingleValue(TestReporter testReporter) { testReporter.publishEntry("a key", "a value"); } @Test void reportSeveralValues(TestReporter testReporter) { HashMap<String, String> values = new HashMap<>(); values.put("user name", "dk38"); values.put("award year", "1974"); testReporter.publishEntry(values); } } |
Test Interfaces & Default Methods
- JUnit Jupitoer 는 @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @TestTemplate, @BeforeEach, and @AfterEach 가 선언된 인터페이스에 default method 선언이 가능하게 해줌.
- @BeforeAll, @AfterAll 는 @TestInstance(Lifecycle.PER_CLASS) 으로 사용가능.
TBD |
Repeated Tests
- JUnit Jupiter는 @RepeatedTest 를 통해 원하는 수만큼의 반복 테스트를 지원.
import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.logging.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.RepetitionInfo; import org.junit.jupiter.api.TestInfo; class RepeatedTestsDemo { private Logger logger = // ... @BeforeEach void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) { int currentRepetition = repetitionInfo.getCurrentRepetition(); int totalRepetitions = repetitionInfo.getTotalRepetitions(); String methodName = testInfo.getTestMethod().get().getName(); logger.info(String.format("About to execute repetition %d of %d for %s", // currentRepetition, totalRepetitions, methodName)); } @RepeatedTest(10) void repeatedTest() { // ... } @RepeatedTest(5) void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) { assertEquals(5, repetitionInfo.getTotalRepetitions()); } @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") @DisplayName("Repeat!") void customDisplayName(TestInfo testInfo) { assertEquals(testInfo.getDisplayName(), "Repeat! 1/1"); } @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME) @DisplayName("Details...") void customDisplayNameWithLongPattern(TestInfo testInfo) { assertEquals(testInfo.getDisplayName(), "Details... :: repetition 1 of 1"); } @RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}") void repeatedTestInGerman() { // ... } } |
JUnit은 코드의 동작을 확인할 수도 있지만 source 코드 변경 시 side-effect를 최소화할 수 있다.
하지만 코드 변경 후 원하는 테스트만 진행하고자 할 경우 아래와 같이 옵션들을 주고 특정한 테스트 케이스들만 진행할 수 있다는 것.
기억해두어야 함.
gradlew test --tests com.test.myTest
gradlew test --tests *MyTest.check
gradlew test --tests *MySomeTest
gradlew test --tests *IntegrationTest
gradlew test --tests *IntegrationTest*funtion*
gradlew someTestTask --tests *IntegrationTest functionalTestTask --tests *APITest*msa
가회동은 참 알수 없는 곳이다.
현대와 전통이 잘 어울려있는, 도심 한복판에 살아있는 오아시스 같은 그런 곳이다.
내게 소소한 행복을 주는..
'사진 > Leica_일상' 카테고리의 다른 글
라이카 Q 영입 기념 (0) | 2016.10.05 |
---|---|
신기한 이동식 칵테일바 (0) | 2016.06.18 |
서판교 단독주택 (0) | 2016.06.17 |
라이카 new x 영입 기념 (0) | 2016.06.14 |