2018. 7. 15. 16:14

junit5



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 
    1. JVM 기반 테스트 프레임워크를 실행하기 위한 기반 모듈.
    2. 플랫폼을 기반으로 실행 가능한 모든 테스트 진행을 위한 테스트 엔진을 제공하고 `Console Launcher` 를 통해 Gradle, Maven 과 연동 가능하게 제공.
    3. 기존 JUnit4 이하 환경에서의 테스트를 위해 Runner를 제공.
    • Junit Jupiter
    1. JUnit 5 기반의 테스트를 작성하기 위한 모듈.
    • Junit Vintage
    1. JUnit4 이하의 테스트를 실행시키기 위한 모듈.
           JUnit5는 실행 시에 Java8 이상이 필요하지만 compile 은 가능하다.
  • Installation : 의존성 정보
    • Junit Platform
      • Group ID: : org.junit.platform
    Artifact ID
    Description
    junit-platform-commons
    JUnit 자체적으로 사용하고 있는 공통 라이브러리.
    junit-platform-console
    Console을 통해 수행되는 테스트 지원.
    junit-platform-console-standalone
    Console Launcher를 통한 Jar 파일 실행 지원.
    junit-platform-engine
    JUnit 5 기반의 테스트 지원.
    junit-platform-gradle-plugin
    Gradle을 통한 테스트 지원.
    junit-platform-launcher
    IDE에서 테스트 플랜 구성 및 실행하는데 사용.
    junit-platform-runner
    JUnit4 기반의 테스트 지원.
    junit-platform-suite-api
    Test suite 구성을 위한 annotation 지원.
    junit-platform-surefire-provider
    Maven surfire 지원.

    • JUnit Jupiter 
      • Group IDorg.junit.jupiter
    Artifact ID
    Description
    junit-jupiter-api
    테스트 케이스 작성 및 확장을 위한 API
    junit-jupiter-engine
    Jupiter 모듈의 TestEngine
    junit-jupiter-params
    Parameterized 테스트 지원을 위한 라이브러리
    junit-jupiter-migrationsupport
    JUnit4에서의 마이그레이션 지원.

    • JUnit Jupiter 
      • Group IDorg.junit.vintage
    Artifact ID
    Description
    junit-vintage-engine
    JUnit4 이하에서 작성된 테스트 케이스를 실행시킬때 사용되는 테스트 엔진.


Annotation

  •  JUnit Jupiter에서 테스트 확장을 위해 다양한 annotation을 제공.
  •  junit-jupiter-api 모듈의 `org.junit.jupiter.api` 패키지에 정의됨.

  • annotation
    desctiption
    상속 여부
    JUnit4
    변경 사항.
    @Test
    테스트 메소드임을 표시함. 상속 됨.
    override 되지 않으면
    @Test
    추가 인자를 사용하지 않음.(ex. expected)
    @ParameterizedTest
    인자를 제공받아 진행되는 테스트임을 나타냄.
    override 되지 않으면
    신규

    @RepeatedTest
    반복 테스트를 위한 테스트 템플릿.
    override 되지 않으면
    신규

    @TestFactory
    동적 테스트 수행을 위한 TestFactory임을 나타냄.
    override 되지 않으면
    신규

    @TestInstance
    Test class 의 생명주기를 설정하기 위한 설정.
    상속됨
    신규

    @TestTemplate
    Provider에 의해 여러번 호출될수 있도록 설계됨. 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

    @ExtendWith
    custom 확장을 등록할 때 사용.
    상속됨



  • 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 {

        @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
        }

        @AfterEach
        void tearDown() {
        }

        @AfterAll
        static 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() {
        // ...
    }

}



2018. 1. 10. 17:17

Junit test 옵션

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


2016. 11. 3. 12:59

삼청동 길

가회동은 참 알수 없는 곳이다.

현대와 전통이 잘 어울려있는, 도심 한복판에 살아있는 오아시스 같은 그런 곳이다.

내게 소소한 행복을 주는..





'사진 > Leica_일상' 카테고리의 다른 글

라이카 Q 영입 기념  (0) 2016.10.05
신기한 이동식 칵테일바  (0) 2016.06.18
서판교 단독주택  (0) 2016.06.17
라이카 new x 영입 기념  (0) 2016.06.14