{"id":20256219,"url":"https://github.com/gihyeon6394/junit5-action","last_synced_at":"2025-08-10T12:12:04.067Z","repository":{"id":175736720,"uuid":"654381262","full_name":"gihyeon6394/Junit5-action","owner":"gihyeon6394","description":"Junit5 getting started","archived":false,"fork":false,"pushed_at":"2023-06-22T04:31:00.000Z","size":3254,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-31T17:50:05.600Z","etag":null,"topics":["junit","junit5","tdd","testing"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gihyeon6394.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-16T02:25:17.000Z","updated_at":"2023-06-16T05:44:48.000Z","dependencies_parsed_at":null,"dependency_job_id":"d65c6e35-1aaa-415c-945d-1d924d7ad8a7","html_url":"https://github.com/gihyeon6394/Junit5-action","commit_stats":null,"previous_names":["gihyeon6394/junit5-action"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gihyeon6394/Junit5-action","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gihyeon6394%2FJunit5-action","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gihyeon6394%2FJunit5-action/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gihyeon6394%2FJunit5-action/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gihyeon6394%2FJunit5-action/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gihyeon6394","download_url":"https://codeload.github.com/gihyeon6394/Junit5-action/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gihyeon6394%2FJunit5-action/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269720750,"owners_count":24464369,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["junit","junit5","tdd","testing"],"created_at":"2024-11-14T10:45:53.857Z","updated_at":"2025-08-10T12:12:04.039Z","avatar_url":"https://github.com/gihyeon6394.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JUnit 5\n\n[Junit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/#overview)를 참고하며,  \n부분적으로 JUnit in Action 3rd Edition \u003csub\u003ewritten Cătălin Tudose\u003c/sub\u003e을 참고했다.\n\n## What is JUnit5?\n\n\u003cimg src=\"img_1.png\"  width=\"70%\"/\u003e\n\nJUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage\n\n- JUnit Platform: foundation for launching testing framework on JVM\n    - console launcher, JUnit Platform Suit Engine\n    - exist in IDE \u003csub\u003eIntelliJ, Eclipse, Visual Studio Code\u003c/sub\u003e\n- JUnit Jupiter: combination of the programming model and extension model\n    - provide `TestEngine`\n- JUnit Vintage: provide `TestEngine` for JUnit 3 and 4\n\n### Demo\n\n```java\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport example.util.Calculator;\nimport org.junit.jupiter.api.Test;\n\nclass MyFirstJUnitJupiterTests {\n\n    private final Calculator calculator = new Calculator();\n\n    @Test\n    void addition() {\n        assertEquals(2, calculator.add(1, 1));\n    }\n\n}\n```\n\n## Definition\n\n- Platform\n    - Container: 트리에서 `test class`를 가지는 컨테이너\n    - Test : 테스트 트리의 노드 (e.g.a `@Test` method)\n- Jupiter\n    - Lifecycle Method : `@BeforeAll`, `@AfterAll`, `@BeforeEach`, `@AfterEach` 가 있는 메서드\n    - Test Class : 최소 1개 이상의 테스트 메서드를 가지는 클래스 \u003csub\u003etop-level class, static member class or `@Nested` class\u003c/sub\u003e\n    - Test Method : `@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, `@TestTemplate` 가 있는 인스턴스 메서드\n\n## Test Classes and Methods\n\n- Teest Method와 Lifecycle Method는 테스트 클래스의 로컬로 선언되거나\n    - 수퍼클래스에서 상속되거나\n    - 인터페이스에서 상속 될 수 있음\n- Test Method와 Lifecycle Method는 `abstract`일 수 없음\n\n### Display Names\n\n`@DisplayName` : 테스트 클래스와 테스트 메서드에 사용할 수 있는 테스트 이름을 지정\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\n\n@DisplayName(\"A special test case\")\nclass DisplayNameDemo {\n\n    @Test\n    @DisplayName(\"Custom test name containing spaces\")\n    void testWithDisplayNameContainingSpaces() {\n    }\n\n    @Test\n    @DisplayName(\"╯°□°）╯\")\n    void testWithDisplayNameContainingSpecialCharacters() {\n    }\n\n    @Test\n    @DisplayName(\"😱\")\n    void testWithDisplayNameContainingEmoji() {\n    }\n\n}\n```\n\n\u003c/details\u003e\n\n### Display Name Generators\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.*;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\npublic class DisplayNameGeneratorDemo {\n\n    @Nested\n    @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)\n    class A_year_is_not_supported {\n\n        @Test\n        void if_it_is_zero() {\n        }\n\n        @DisplayName(\"A negative value for year is not supported by the leap year computation.\")\n        @ParameterizedTest(name = \"For example, year {0} is not supported.\")\n        @ValueSource(ints = {-1, -4})\n        void if_it_is_negative(int year) {\n        }\n    }\n\n    @Nested\n    @IndicativeSentencesGeneration(separator = \" -\u003e \", generator = DisplayNameGenerator.ReplaceUnderscores.class)\n    class A_year_is_a_leap_year {\n\n        @Test\n        void if_it_is_divisible_by_4_but_not_by_100() {\n        }\n\n        @ParameterizedTest(name = \"Year {0} is a leap year.\")\n        @ValueSource(ints = {2016, 2020, 2048})\n        void if_it_is_one_of_the_following_years(int year) {\n        }\n    }\n}\n\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo result\u003c/summary\u003e\n\n  \u003cimg src=\"img_2.png\"  width=\"70%\"/\u003e\n\n\u003c/details\u003e\n\n### Display Name 우선순위\n\n1. `@DisplayName`\n2. `@DisplayNameGeneration`의 `DisplayNameGenerator`\n3. 설정 파라미터의 `DisplayNameGenerator`\n4. `org.junit.jupiter.api.DisplaNameGenerator.Standard`\n\n## Assertions\n\n- `org.junit.jupiter.api.Assertions` : 테스트 결과를 검증하는 메서드를 제공\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n````java\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\n\nimport static java.time.Duration.ofMillis;\nimport static java.time.Duration.ofMinutes;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@DisplayName(\"Assert static methods demo\")\npublic class AssertionsDemo {\n\n    private final Calculator cal = new Calculator();\n    private final Idol karina = new Idol.Builder(\"카리나\", 20).isLeader(1).build();\n\n    @Test\n    @DisplayName(\"Standard assertions\")\n    void standardAssertions() {\n        assertEquals(2, cal.add(1, 1));\n        assertEquals(4, cal.multiply(2, 2),\n                \"The optional failure message is now the last parameter\");\n        assertEquals(\"카리나\", karina.getMemberName(), () -\u003e karina.toString() + \"의 이름이 카리나인지 확인\");\n    }\n\n    @Test\n    @DisplayName(\"Grouped assertions (Failures)\")\n    void groupAssertions() {\n\n        assertAll(\"karina\",\n                () -\u003e assertEquals(\"카리나\", karina.getMemberName()),\n                () -\u003e assertEquals(20, karina.getAge()));\n\n    }\n\n    @Test\n    void dependentAssertions() {\n\n        assertAll(\"karina variables\",\n                () -\u003e {\n                    String name = karina.getMemberName();\n                    assertNotNull(name);\n                    assertAll(\"name conventions rule\",\n                            () -\u003e assertEquals(3, name.length()),\n                            () -\u003e assertTrue(name.startsWith(\"카\")));\n\n                },\n                () -\u003e {\n                    int age = karina.getAge();\n                    assertAll(\"age conventions rule\",\n                            () -\u003e assertTrue(age \u003e 0),\n                            () -\u003e assertTrue(age \u003c 30));\n                });\n    }\n\n    @Test\n    void exceptionTesting() {\n        Exception exception = assertThrows(ArithmeticException.class, () -\u003e\n                cal.divide(1, 0));\n        assertEquals(\"/ by zero\", exception.getMessage());\n    }\n\n    @Test\n    @DisplayName(\"timeoutNotExceeded\")\n    void timeoutNotExceeded() {\n        String actualResult = assertTimeout(ofMinutes(2), () -\u003e {\n            return \"a result\";\n        });\n        assertEquals(\"a result\", actualResult);\n    }\n\n    @Test\n    @DisplayName(\"timeoutNotExceededWithMethod\")\n    void timeoutNotExceededWithMethod() {\n        String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting);\n        assertEquals(\"Hello, World!\", actualGreeting);\n    }\n\n    private static String greeting() {\n        return \"Hello, World!\";\n    }\n\n    @Test\n    @DisplayName(\"timeoutExceeded\")\n    void timeoutExceeded() {\n        assertTimeout(ofMillis(10), () -\u003e {\n            Thread.sleep(100);\n        });\n    }\n\n    @Test\n    void timeoutExceededWithPreemptiveTermination() {\n        // The following assertion fails with an error message similar to:\n        // execution timed out after 10 ms\n        assertTimeoutPreemptively(ofMillis(10), () -\u003e {\n            // Simulate task that takes more than 10 ms.\n            new CountDownLatch(1).await();\n        });\n    }\n\n}\n\n````\n\n\u003c/details\u003e\n\n### third-party assertion library\n\n- JUnit 팀도 third-party assertion library를 사용하는 것을 권장\n    - ex. `AssertJ`, `Hamcrest`, `Truth`\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\n@DisplayName(\"Assert static methods demo\")\npublic class AssertionsDemo {\n\n    private final Idol karina = new Idol.Builder(\"카리나\", 20).isLeader(1).build();\n\n    @Test\n    @DisplayName(\"third-party library\")\n    void thirdPartyLib() {\n        assertThat(karina.getAge(), is(equalTo(20)));\n    }\n\n}\n```\n\n\u003c/details\u003e\n\n## Assumptions\n\n- 테스트의 진행 조건 부여 가능\n- 조건이 만족되지 않으면 테스트는 실행되지 않음\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.junit.jupiter.api.Assumptions.assumingThat;\n\npublic class AssumptionsDemo {\n\n    private final Idol karina = new Idol.Builder(\"카리나\", 20).isLeader(1).build();\n\n    @Test\n    @DisplayName(\"test only on ci server\")\n    void testOnlyCiServer() {\n        assumeTrue(\"CI\".equals(System.getenv(\"ENV\")));\n    }\n\n    @Test\n    @DisplayName(\"test only on dev server\")\n    void testOnlyDevServer() {\n        assumeTrue(\"DEV\".equals(System.getenv(\"ENV\")),\n                () -\u003e \"Aborting test: not on developer workstation\");\n    }\n\n    @Test\n    @DisplayName(\"CI server test and All environment test\")\n    void testInAllEnvironments() {\n        assumingThat(\"CI\".equals(System.getenv(\"ENV\")),\n                () -\u003e {\n                    // perform only in CI server\n                    assertEquals(20, karina.getAge());\n                });\n\n        assertEquals(\"카리나\", karina.getMemberName());\n    }\n}\n```\n\n\u003c/details\u003e\n\n## Disabling Tests\n\n- `@Disabled` 어노테이션을 사용하여 테스트를 비활성화\n- 클래스, 메서드 레벨에 가능\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\npublic class AssumptionsDemo {\n\n    @Test\n    @Disabled(\"Disabled until ready for production server\")\n    void testProduction() {\n\n    }\n}\n```\n\n\u003c/details\u003e\n\n## Conditional Test Execution \u003csup\u003e조건부 테스트 실행\u003c/sup\u003e\n\n- 프로그래밍적인 조건부로 테스트 실행\n- `org.junit.jupiter.api.condition` 패키지 : 컨테이너나 테스트를 선언적오르 사용 / 미사용 처리 가능\n\n### OS, Architecture 조건\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n````java\npublic class ConditionTest {\n\n    @TestOnMac\n    void testOnMac() {\n    }\n\n    @Test\n    @EnabledOnOs({OS.LINUX, OS.MAC})\n    void onLinuxOrMac() {\n    }\n\n    @Test\n    @EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9})\n    void onlyOnJava89() {\n    }\n\n    @Test\n    @EnabledIfSystemProperty(named = \"os.arch\", matches = \".*64.*\")\n    void onlyOn64BitArchitectures() {\n    }\n\n    @Test\n    @DisabledIf(\"customCondition\")\n    void disabled() {\n    }\n\n    boolean customCondition() {\n        return true;\n    }\n\n    @Test\n    @EnabledIf(\"example.ExternalCondition#customCondition\")\n    void enabled() {\n    }\n\n}\n// ...\n\nclass ExternalCondition {\n    static boolean customCondition() {\n        return true;\n    }\n\n}\n\n````\n\n\u003c/details\u003e\n\n- `@EnabledOnOs`, `@DisabledOnOs`\n- `@EnabledOnJre`, `@DisabledOnJre`\n- `@EnabledOnJreRange`, `@DisabledOnJreRange`\n- `@EnabledInNativeImage`, `@DisabledInNativeImage` : GraalVM Native Image\n- `@EnabledIfSystemProperty`, `@DisabledIfSystemProperty`\n\n--- \n\n## Tagging and Filtering \u003csup\u003e태그와 필터링\u003c/sup\u003e\n\n- `@Tag` 어노테이션을 사용하여 테스트를 태그하고,\n- 테스트 시 필터링 가능\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(\"fast\")\n@Tag(\"model\")\nclass TaggingDemo {\n\n    @Test\n    @Tag(\"karina\")\n    void testingKarina() {\n    }\n\n    @Test\n    void testSomething() {\n    }\n\n}\n\n```\n\n\u003c/details\u003e\n\n\u003cimg src=\"img_3.png\"  width=\"40%\"/\u003e\n\n## Test Execution Order \u003csup\u003e테스트 실행 순서\u003c/sup\u003e\n\n- default, 테스트 순서는 보장되지 않는 알고리즘에 의해 진행\n- 테스트 순서 지정 가능\n\n### Method Order\n\n- 테스트에 순서가 필요할 떄가 있음\n    - ex. 통합 테스트, 기능 테스트와 같이 단위의 순서가 중요할 떄\n- `@TestMethodOrder` 어노테이션을 사용하여 테스트 순서 지정 가능\n    - `MethodOrderer.DisplayName` : 테스트 메서드 이름 순서\n    - `MethodOrderer.MethodName` : 테스트 메서드 이름 순서\n    - `MethodOrderer.Random` : 무작위 순서\n    - `MethodOrderer.OrderAnnotation` : `@Order` 어노테이션을 사용하여 테스트 순서 지정\n    - ~~`MethodOrderer.Alphanumeric` : 알파벳 순서~~ \u003csub\u003edeprecated since 6.0\u003c/sub\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.*;\n\n@TestMethodOrder(MethodOrderer.OrderAnnotation.class)\nclass OrderedTestsDemo {\n\n    @Test\n    @Order(1)\n    void first() {\n    }\n\n    @Test\n    @Order(2)\n    void second() {\n    }\n\n    @Test\n    @Order(3)\n    void third() {\n    }\n}\n\n```  \n\n\u003c/details\u003e\n\n### Class Order\n\n테스트 클래스의 순서가 필요할 때가 있음\n\n- fail fast mode\n- shortest test plan execution duration mode\n- 그 외 다수\n\n`junit.jupiter.testclass.order.default`의 `ClassOrderer`\n\n- `ClassOrder.ClassName` : 클래스 이름을 기준으로 순서 설정\n- `ClassOrder.DisplayName` : DisplayName을 기준으로 순서 설정\n- `ClassOrder.OrderAnnotation` : `@Order` 어노테이션을 사용하여 테스트 순서 지정\n- `ClassOrder.Random` : 무작위 순서\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.*;\n\n@TestClassOrder(ClassOrderer.OrderAnnotation.class)\npublic class OrderedNestedTestClassesDemo {\n\n    @Nested\n    @Order(1)\n    class FirstTest {\n        @Test\n        void test1() {\n        }\n    }\n\n    @Nested\n    @Order(2)\n    class SecondTest {\n        @Test\n        void test2() {\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n## Test Instance Lifecycle\n\n기본값은 `Lifecycle.PER_CLASS` : 메서드마다 테스트 클래스 인스턴스 생성\n\n### per-class mode\n\n- 클래스의 모든 테스트 메서드를 해당 클래스의 단일 테스트 인스턴스에서 실행\n- `@TestInstance(Lifecycle.PER_CLASS)`\n- non-static 메서드, interface default 메서드, `@Nested` 클래스의 메서드에 `@BeforeAll`, `@AfterAll` 선언 가능\n\n### Changing the Default Test Instance Lifecycle\n\n- ex. `-Djunit.jupiter.testinstance.lifecycle.default=per_class`\n- JVM 시스템 파라미터로서 할당 가능\n- `src/test/resources/junit-platform.properties` 파일에 할당 가능\n    - 버전관리 가능해서 용이한 방법\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInstance;\n\n@TestInstance(TestInstance.Lifecycle.PER_CLASS)\npublic class LifecycleTest {\n\n    private int flag = 0;\n\n    @Test\n    void plus1() {\n        flag++;\n    }\n\n    @Test\n    void plus2() {\n        flag++;\n    }\n}\n```\n\n\u003c/details\u003e\n\n## Nested Tests\n\n- `@Nested` : 테스트 메서드를 그룹화\n- Outer class가 실행되고 Inner Class가 실행됨\n- Outer class의 setup code는 항상 실행됨\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.EmptyStackException;\nimport java.util.Stack;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n@DisplayName(\"A stack\")\npublic class TestingAStackDemo {\n\n    private Stack\u003cObject\u003e stack;\n\n    @Test\n    @DisplayName(\"is instantiated with new Stack()\")\n    void isInstantiatedWithNew() {\n        new Stack\u003c\u003e(); // stack 인스턴스 생성\n    }\n\n    @Nested\n    @DisplayName(\"when new\")\n    class WhenNew {\n\n        @BeforeEach\n        void createNewStack() {\n            stack = new Stack\u003c\u003e();\n        }\n\n        @Test\n        @DisplayName(\"is empty\")\n        void isEmpty() {\n            assertTrue(stack.isEmpty());\n        }\n\n        @Test\n        @DisplayName(\"throws EmptyStackException when popped\")\n        void throwsExceptionWhenPopped() {\n            assertThrows(EmptyStackException.class, stack::pop);\n        }\n\n        @Test\n        @DisplayName(\"throws EmptyStackException when peeked\")\n        void throwsExceptionWhenPeeked() {\n            assertThrows(EmptyStackException.class, stack::peek);\n        }\n\n        @Nested\n        @DisplayName(\"after pushing an element\")\n        class AfterPushing {\n            String anElement = \"an element\";\n\n            @BeforeEach\n            void pushAnElement() {\n                stack.push(anElement);\n            }\n\n            @Test\n            @DisplayName(\"it is no longer empty\")\n            void isNotEmpty() {\n                assertFalse(stack.isEmpty());\n            }\n\n            @Test\n            @DisplayName(\"returns the element when peeked but remains not empty\")\n            void returnElementWhenPeeked() {\n                assertEquals(anElement, stack.peek());\n                assertFalse(stack.isEmpty());\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo result\u003c/summary\u003e\n\n\u003cimg src=\"img_4.png\"  width=\"50%\"/\u003e\n\n\u003c/details\u003e\n\n## DI for Constructors and Methods\n\n- JUnit Jupiter는 테스트 클래스의 생성자와 테스트 메서드에 DI를 지원\n- `ParameterResolver` API : 런타임에 동적으로 파라미터 resolve\n- `TestInfoParameterResolver` : 최근 컨테이너나 테스트의 속성을 제곰 \u003csub\u003eex. `displayName`\u003c/sub\u003e\n- `RepititionInfoParameterResolver` : `@RepeatedTest`에서만 사용 가능\n- `TestReporterParameterResolver` : 테스트 실행에 대한 추가적인 정보를 생성 가능\n\n## Test Interfaces and Default Methods\n\n- 테스트 어노테이션을 인터페이스 `default` 메서드에 선언 가능\n    - `@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, `@TestTemplate`, `@BeforeEach`, `@AfterEach`, `@BeforeAll`, `@AfterAll`\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\n// 테스트 클래스\npublic class TestInterfaceDemo implements TestLifecycleLogger, TimeExecutionLogger, TestInterfaceDynamicTestsDemo {\n\n    @Test\n    void isEqualValue() {\n        assertEquals(1, 1, \"is always equal\");\n    }\n}\n\n// 테스트마다 로깅하는 인터페이스\n@TestInstance(Lifecycle.PER_CLASS)\ninterface TestLifecycleLogger {\n\n    static final Logger logger = Logger.getLogger(TestLifecycleLogger.class.getName());\n\n    @BeforeAll\n    default void beforeAllTests() {\n        logger.info(\"Before all tests\");\n    }\n\n    @AfterAll\n    default void afterAllTests() {\n        logger.info(\"After all tests\");\n    }\n\n    @BeforeEach\n    default void beforeEachTest(TestInfo testInfo) {\n        logger.info(() -\u003e String.format(\"About to execute [%s]\",\n                testInfo.getDisplayName()));\n    }\n\n    @AfterEach\n    default void afterEachTest(TestInfo testInfo) {\n        logger.info(() -\u003e String.format(\"Finished executing [%s]\",\n                testInfo.getDisplayName()));\n    }\n}\n\n// 테스트 데이터를 동적으로 반환하는 팩터리\npublic interface TestInterfaceDynamicTestsDemo {\n\n    @TestFactory\n    default Stream\u003cDynamicTest\u003e dynamicTestsForPalindromes() {\n        return Stream.of(\"racecar\", \"radar\", \"mom\", \"dad\")\n                .map(text -\u003e DynamicTest.dynamicTest(text, () -\u003e {\n                    System.out.println(\"text = \" + text);\n                }));\n    }\n}\n\n@Tag(\"timed\")\n@ExtendWith(TimingExtension.class)\npublic interface TimeExecutionLogger {\n}\n\n// 테스트 실행시간을 측정\npublic class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {\n    private static final Logger logger = Logger.getLogger(TimingExtension.class.getName());\n\n    private static final String START_TIME = \"start time\";\n\n\n    @Override\n    public void beforeTestExecution(ExtensionContext extensionContext) throws Exception {\n        getStore(extensionContext).put(START_TIME, System.currentTimeMillis());\n\n    }\n\n    @Override\n    public void afterTestExecution(ExtensionContext extensionContext) throws Exception {\n        Method testMethod = extensionContext.getRequiredTestMethod();\n        long startTime = getStore(extensionContext).remove(START_TIME, long.class);\n        long duration = System.currentTimeMillis() - startTime;\n\n        logger.info(() -\u003e String.format(\"Method [%s] took %s ms.\", testMethod.getName(), duration));\n    }\n\n    private Store getStore(ExtensionContext context) {\n        return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestMethod()));\n    }\n\n}\n\n```\n\n\u003c/details\u003e\n\n#### 실행 순서\n\n1. `TestLifecycleLogger`\n    1. `beforeAllTests()`\n    2. `beforeEach()`\n2. `TimingExtension` \u003e `beforeTestExecution()`\n3. `TestInterfaceDynamicTestsDemo` \u003e `dynamicTestsForPalindromes()`\n\n## Repeated Tests\n\n- `@RepeatedTest` : n번 반복 테스트\n- `currentRepetition`, `totalRepetitions`\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.RepeatedTest;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class RepeatTest {\n\n    @RepeatedTest(value = 10, name = \"{displayName} {currentRepetition}/{totalRepetitions}\")\n    @DisplayName(\"repeat test\")\n    void repeatedTest() {\n        assertEquals(1, 1);\n        System.out.println(\"repeatedTest\");\n    }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.*;\n\nimport java.util.logging.Logger;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class RepeatedTestsDemo {\n\n    private Logger logger = Logger.getLogger(RepeatedTestsDemo.class.getName());\n\n    @BeforeEach\n    void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) {\n        int currentRepetition = repetitionInfo.getCurrentRepetition();\n        int totalRepetitions = repetitionInfo.getTotalRepetitions();\n        String methodName = testInfo.getTestMethod().get().getName();\n        logger.info(String.format(\"About to execute repetition %d of %d for %s\", currentRepetition, totalRepetitions, methodName));\n    }\n\n    @RepeatedTest(10)\n    void repeatedTest() {\n        // ...\n    }\n\n    @RepeatedTest(value = 1, name = \"{displayName} {currentRepetition}/{totalRepetitions}\")\n    @DisplayName(\"Repeat!\")\n    void customDisplayName(TestInfo testInfo) {\n        assertEquals(testInfo.getDisplayName(), \"Repeat! 1/1\");\n    }\n\n    @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME)\n    @DisplayName(\"Details...\")\n    void customDisplayNameWithLongPattern(TestInfo testInfo) {\n        assertEquals(\"Details... :: repetition 1 of 1\", testInfo.getDisplayName());\n    }\n\n    @RepeatedTest(value = 5, name = \"Wiederholung {currentRepetition} von {totalRepetitions}\")\n    void repeatedTestInGerman() {\n        // ...\n    }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo result\u003c/summary\u003e\n\n\u003cimg src=\"img_5.png\"  width=\"40%\"/\u003e\n\n\u003c/details\u003e\n\n## Parameterized Tests\n\n#### `@ValueSource`\n\n- 리터럴 값을 담는 1차배열 가능\n    - `short`, `byte`, `int`, `long`, `float`, `double`, `char`, `boolean`, `java.lang.String`, `java.lang.Class`\n- `@AutoCloseable` 구현체를 사용하려면 `@ParameterizedTest(autoCloseArguments = false)`를 통해 비활성화해서 재사용 가능\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class ParamTest {\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"karina\", \"minzi\", \"hani\"})\n    void test1(String name) {\n        assertTrue(name.length() \u003c 7);\n    }\n\n    @ParameterizedTest\n    @NullAndEmptySource // @NullSource + @EmptySource\n    @ValueSource(strings = {\" \", \"   \", \"\\t\", \"\\n\"})\n    void nullEmptyAndBlankStrings(String text) {\n        assertTrue(text == null || text.trim().isEmpty());\n    }\n}\n```\n\n\u003cimg src=\"img_6.png\"  width=\"30%\"/\u003e\n\n\u003c/details\u003e\n\n#### `@EnumSource`\n\n- Enum 상수 주입\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\n\npublic class ParamTest {\n\n    private Logger logger = Logger.getLogger(ParamTest.class.getName());\n\n    @ParameterizedTest\n    @EnumSource\n    void testWithEnumSourceWithAutoDetection(ChronoUnit unit) {\n        logger.info(unit.toString());\n        assertNotNull(unit);\n    }\n\n\n    @ParameterizedTest\n    @EnumSource(names = {\"DAYS\", \"HOURS\"})\n    void testWithEnumSourceInclude(ChronoUnit unit) {\n        logger.info(unit.toString());\n        assertTrue(EnumSet.of(ChronoUnit.DAYS, ChronoUnit.HOURS).contains(unit));\n    }\n\n    @ParameterizedTest\n    @EnumSource(mode = EXCLUDE, names = {\"ERAS\", \"FOREVER\"})\n    void testEnumSourceExclude(ChronoUnit unit) {\n        logger.info(unit.toString());\n        assertFalse(EnumSet.of(ChronoUnit.ERAS, ChronoUnit.FOREVER).contains(unit));\n    }\n\n    @ParameterizedTest\n    @EnumSource(mode = MATCH_ALL, names = \"^.*DAYS$\")\n    void testWithEnumSourceRegex(ChronoUnit unit) {\n        assertTrue(unit.name().endsWith(\"DAYS\"));\n    }\n\n}\n```\n\n\u003c/details\u003e\n\n#### `@MethodSource`\n\n- 1개 이상의 팩터리 메서드를 참조\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.extension.*;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.List;\nimport java.util.logging.Logger;\nimport java.util.stream.IntStream;\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\n\npublic class ParamTest {\n\n    private Logger logger = Logger.getLogger(ParamTest.class.getName());\n\n    @ParameterizedTest\n    @MethodSource(\"idolProvider\")\n    void testWithExplicitLocalMethodSource(String argument) {\n        logger.info(argument);\n        assertNotNull(argument);\n    }\n\n    static Stream\u003cString\u003e idolProvider() {\n        return Stream.of(\"karina\", \"minzi\", \"hani\");\n    }\n\n    @ParameterizedTest\n    @MethodSource\n    void testWithDefaultLocalMethodSource(String argument) {\n        logger.info(argument);\n        assertNotNull(argument);\n    }\n\n    static Stream\u003cString\u003e testWithDefaultLocalMethodSource() {\n        return Stream.of(\"karina\", \"minzi\", \"hani\");\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"idolGroupProvider\")\n    void testWithMultiArgMethodSource(String groupName, int num, List\u003cString\u003e memberList) {\n        assertNotNull(groupName);\n        assertTrue(num \u003e= 1);\n        assertTrue(memberList.size() \u003e 0);\n    }\n\n    static Stream\u003cArguments\u003e idolGroupProvider() {\n        return Stream.of(\n                Arguments.of(\"NewJeans\", 1, List.of(\"hani\", \"minzi\")),\n                Arguments.of(\"IVE\", 2, List.of(\"Rei\")),\n                Arguments.of(\"IU\", 3, List.of(\"solo\"))\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"IdolProvider#idolGroupProvider\")\n    void testWithExternalMethodSource(String groupName, int num, List\u003cString\u003e memberList) {\n        assertNotNull(groupName);\n        assertTrue(num \u003e= 1);\n        assertTrue(memberList.size() \u003e 0);\n    }\n\n    class IdolProvider {\n        static Stream\u003cArguments\u003e idolGroupProvider() {\n            return Stream.of(\n                    Arguments.of(\"NewJeans\", 1, List.of(\"hani\", \"minzi\")),\n                    Arguments.of(\"IVE\", 2, List.of(\"Rei\")),\n                    Arguments.of(\"IU\", 3, List.of(\"solo\"))\n            );\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"range\")\n    void testWIthRangeMethodSource(int argument) {\n        logger.info(\"argument : \" + argument);\n        assertNotEquals(9, argument);\n    }\n\n    static IntStream range() {\n        return IntStream.range(0, 20).skip(10);\n    }\n\n    @RegisterExtension // IntegerResolver 확장\n    static final IntegerResolver integerResolver = new IntegerResolver();\n\n    @ParameterizedTest\n    @MethodSource(\"factoryMethodWithArguments\")\n    void testWithFactoryMethodWithArguments(String argument) {\n        assertTrue(argument.startsWith(\"2\"));\n    }\n\n    static Stream\u003cArguments\u003e factoryMethodWithArguments(int quantity) {\n        return Stream.of(\n                arguments(quantity + \" apples\"),\n                arguments(quantity + \" lemons\")\n\n        );\n    }\n\n    static class IntegerResolver implements ParameterResolver {\n\n        @Override\n        public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {\n            return parameterContext.getParameter().getType() == int.class;\n        }\n\n        @Override\n        public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {\n            return 2;\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### `@CsvSource`\n\n- CSV 포맷의 문자열을 주입\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport java.util.logging.Logger;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\n/**\n * @ParameterizedTest 예시\n */\npublic class ParamTest {\n\n    private Logger logger = Logger.getLogger(ParamTest.class.getName());\n\n    @ParameterizedTest\n    @CsvSource({\"karina, Aespa\", \"minzi, NewJeans\", \"hani, NewJeans\"})\n    void testWithCsvSource(String name, String group) {\n        logger.info(name + \" \" + group);\n        assertNotNull(name);\n        assertNotNull(group);\n    }\n\n    @ParameterizedTest(name = \"[{index}] {arguments}\")\n    @CsvSource(useHeadersInDisplayName = true, textBlock = \"\"\"\n            name, group\n            karina, Aespa\n            minzi, NewJeans\n            hani, NewJeans\n            \"\"\")\n    void testWithCsvSourceWithHeader(String name, String group) {\n        logger.info(name + \" \" + group);\n        assertNotNull(name);\n        assertNotNull(group);\n    }\n\n    @ParameterizedTest\n    @CsvSource(useHeadersInDisplayName = true, delimiter = '|', quoteCharacter = '\"', textBlock = \"\"\"\n            name| group\n            \"karina\"| \"Aespa\"\n            \"minzi\"| \"NewJeans\"\n            \"hani\"| \"NewJeans\"\n            \"\"\")\n    void testWithCsvSourceWithDelimiterAndQuote(String name, String group) {\n        logger.info(name + \" \" + group);\n        assertNotNull(name);\n        assertNotNull(group);\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### `@CsvFileSource`\n\n- CSV 파일을 주입\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvFileSource;\n\nimport java.util.logging.Logger;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\npublic class ParamTest {\n\n    private Logger logger = Logger.getLogger(ParamTest.class.getName());\n\n    @ParameterizedTest\n    @CsvFileSource(resources = \"resource/Idol.csv\", numLinesToSkip = 1)\n    void testWithCsvFileSource(String name, String group) {\n        logger.info(name + \" \" + group);\n        assertNotNull(name);\n        assertNotNull(group);\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### `@ArgumentsSource`\n\n- `ArgumentsProvider` 구현체를 통해 인자를 주입\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\nimport java.util.logging.Logger;\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\npublic class ParamTest {\n\n    private Logger logger = Logger.getLogger(ParamTest.class.getName());\n\n\n    @ParameterizedTest\n    @ArgumentsSource(IdolArgumentProvider.class)\n    void testWithArgumentsSource(String name) {\n        logger.info(name);\n        assertNotNull(name);\n    }\n\n    static class IdolArgumentProvider implements ArgumentsProvider {\n\n        @Override\n        public Stream\u003c? extends Arguments\u003e provideArguments(ExtensionContext extensionContext) {\n            return Stream.of(\"karina\", \"minzi\", \"hani\").map(Arguments::of);\n        }\n    }\n}\n\n```\n\n\u003c/details\u003e\n\n### Argument Conversion\n\n#### Widening Conversion\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\npublic class ArgumentConversionTest {\n    @ParameterizedTest\n    @ValueSource(ints = {1, 2, 3})\n    void testWithWideningArgumentConversion(double argument) {\n        assertNotNull(argument); // argument = 1.0, 2.0, 3.0\n    }\n}\n\n```\n\n\u003c/details\u003e\n\n#### Implicit  Conversion\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.time.temporal.ChronoUnit;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\npublic class ArgumentConversionTest {\n\n    @ParameterizedTest\n    @ValueSource(strings = \"SECONDS\")\n    void testWithImplicitArgumentConversion(ChronoUnit argument) {\n        assertNotNull(argument.name()); // argument.Enum.name = SECONDS\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### Explicit Conversion\n\n- `ArgumentConverter` 구현체와 `@ConvertWith` 어노테이션을 이용\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.converter.ArgumentConversionException;\nimport org.junit.jupiter.params.converter.ConvertWith;\nimport org.junit.jupiter.params.converter.JavaTimeConversionPattern;\nimport org.junit.jupiter.params.converter.SimpleArgumentConverter;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.time.LocalDate;\nimport java.time.temporal.ChronoUnit;\nimport java.util.logging.Logger;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\npublic class ArgumentConversionTest {\n\n    private Logger logger = Logger.getLogger(ArgumentConversionTest.class.getName());\n\n\n    @ParameterizedTest\n    @EnumSource(ChronoUnit.class)\n    void testWithExplicitArgumentConversion(@ConvertWith(ToStringArgumentConverter.class) String argument) {\n        logger.info(\"argument = \" + argument); // argument = SECONDS\n        assertNotNull(argument); // argument = ChronoUnit\n    }\n\n    private static class ToStringArgumentConverter extends SimpleArgumentConverter {\n        @Override\n        protected Object convert(Object o, Class\u003c?\u003e aClass) throws ArgumentConversionException {\n            assertEquals(String.class, aClass, \"Can only convert to String\");\n            if (o instanceof Enum\u003c?\u003e) {\n                return ((Enum\u003c?\u003e) o).name();\n            }\n            return String.valueOf(o);\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"01.01.2017\", \"31.12.2017\"})\n    void testWithExplicitJavaTimeConverter(@JavaTimeConversionPattern(\"dd.MM.yyyy\") LocalDate argument) {\n        assertEquals(2017, argument.getYear());\n    }\n}\n```\n\n\u003c/details\u003e\n\n### Argument Aggregation\n\n- `ArgumentsAccessor` 구현체\n- `@interface`으로 커스텀 가능\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.extension.ParameterContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.aggregator.AggregateWith;\nimport org.junit.jupiter.params.aggregator.ArgumentsAccessor;\nimport org.junit.jupiter.params.aggregator.ArgumentsAggregationException;\nimport org.junit.jupiter.params.aggregator.ArgumentsAggregator;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.time.LocalDate;\nimport java.util.logging.Logger;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\npublic class ArgumentConversionTest {\n\n    private Logger logger = Logger.getLogger(ArgumentConversionTest.class.getName());\n\n    @ParameterizedTest\n    @CsvSource({\n            \"Karina, 20, 1, 2000-04-11\",\n            \"Minzi, 17, 1, 2004-05-07\",\n    })\n    void testWithArgumentAccessor(ArgumentsAccessor arguments) {\n        Idol idol = new Idol.Builder(arguments.getString(0), arguments.getInteger(1))\n                .isLeader(arguments.getInteger(2)).birthDate(arguments.get(3, LocalDate.class))\n                .build();\n\n        assertNotNull(idol);\n        logger.info(idol.toString());\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n            \"Karina, 20, 1, 2000-04-11\",\n            \"Minzi, 17, 1, 2004-05-07\",\n    })\n    void testWithArgumentsAggregator(@AggregateWith(IdolAggregator.class) Idol idol) {\n        assertNotNull(idol);\n        logger.info(idol.toString());\n    }\n\n\n    private static class IdolAggregator implements ArgumentsAggregator {\n\n        @Override\n        public Idol aggregateArguments(ArgumentsAccessor argumentsAccessor, ParameterContext parameterContext) throws ArgumentsAggregationException {\n            return new Idol.Builder(argumentsAccessor.getString(0), argumentsAccessor.getInteger(1))\n                    .isLeader(argumentsAccessor.getInteger(2)).birthDate(argumentsAccessor.get(3, LocalDate.class))\n                    .build();\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n            \"Karina, 20, 1, 2000-04-11\",\n            \"Minzi, 17, 1, 2004-05-07\",\n    })\n    void testWithArgumentsAggregatorAnnotation(@CsvToIdol Idol idol) {\n        assertNotNull(idol);\n        logger.info(idol.toString());\n    }\n\n\n    @Retention(RetentionPolicy.RUNTIME)\n    @Target(ElementType.PARAMETER)\n    @AggregateWith(IdolAggregator.class)\n    private @interface CsvToIdol {\n    }\n\n}\n\n```\n\n\u003c/details\u003e\n\n### Customizing Display Name\n\n- `displayName` : 메서드 이름\n- `index` : invocation index\n- `arguments` : comma-separated 인수\n- `argumentsWithNames` : 인수 + 파라미터 이름\n- `{0}`, `{1}` : 각 인수\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.logging.Logger;\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Named.named;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\n\npublic class CustomDisplayNameTest {\n\n    private Logger logger = Logger.getLogger(CustomDisplayNameTest.class.getName());\n\n    @DisplayName(\"Display name of container\")\n    @ParameterizedTest(name = \"{index} ==\u003e the age of ''{0}'' is {1}\")\n    @CsvSource({\"Karina, 22\", \"Minzi, 18\"})\n    void testWithCustomDisplayNames(String name, int age) {\n        assertTrue(age \u003e 0);\n        logger.info(\"name: \" + name + \", age: \" + age);\n    }\n\n    @DisplayName(\"A parameterized test with custom display name\")\n    @ParameterizedTest(name = \"{index}: {0}\")\n    @MethodSource(\"namedArguments\")\n    void testWithNamedArguments(int age) {\n        logger.info(\"age : \" + age);\n        assertTrue(age \u003e 0);\n    }\n\n    static Stream\u003cArguments\u003e namedArguments() {\n        return Stream.of(\n                arguments(named(\"karina age\", 22))\n                , arguments(named(\"Minzi age\", 18))\n        );\n    }\n}\n```\n\n\u003c/details\u003e\n\n### Lifecycle and Interoperability\n\n- `@ParameterizedTest`는 `@Test`와 동일한 라이프사이클을 가짐\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.TestInfo;\nimport org.junit.jupiter.api.TestReporter;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.logging.Logger;\n\npublic class LifeCycleInteroperablilityTest {\n\n    private Logger logger = Logger.getLogger(LifeCycleInteroperablilityTest.class.getName());\n\n    @BeforeEach\n    void beforeEach(TestInfo testInfo) {\n        logger.info(\"beforeEach() testInfo : \" + testInfo.toString());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = \"apple\")\n    void testWithRegularParameterResolver(String argument, TestReporter testReporter) {\n        testReporter.publishEntry(\"argument\", argument);\n        logger.info(\"testWithRegularParameterResolver() testReporter : \" + testReporter);\n    }\n\n    @AfterEach\n    void afterEach(TestInfo testInfo) {\n        logger.info(\"AfterEach() testInfo : \" + testInfo.toString());\n    }\n}\n```\n\n\u003c/details\u003e\n\n## Dynamic Tests\n\n- 정적인 테스트\n    - 일반적인 JUnit 테스트\n    - comile-time에 테스트 내용이 정해지고,\n    - run time에는 수정 불가\n- `DynamicTest` : run time에 생성되는 테스트\n    - 동적인 테스트는 반드시펙터리 메서드에 의해 생성되어야함\n\n#### `@TestFactory`\n\n- 테스트 케이스를 지원하는 펙터리\n- 다이나믹 테스트는 팩터리의 산출물\n- 단일 `DynamicNode` 또는 `Stream`, `Collection`, `Iterable`, `Iterator`를 반환\n- `private`, `static` 불가\n\n## Timeouts\n\n- 테스트 진행 시간을 테스트 성공 조건에 줌\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class TimeoutDemo {\n\n    @BeforeEach\n    @Timeout(5)\n    void setUp() {\n        // fails if execution time exceeds 5 seconds\n    }\n\n    @Test\n    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)\n    void failsIfExecutionTimeExceeds500Milliseconds() {\n        // fails if execution time exceeds 500 milliseconds\n    }\n\n    @Test\n    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)\n    void failsIfExecutionTimeExceeds500MillisecondsWithSeparateThread() {\n        // this test will be executed in a separate thread\n    }\n}\n```\n\n\u003c/details\u003e\n\n## Parallel Execution\n\n- 기본적으로 single thread, 순차적 실행\n- Parallel은 5.3부터 가능\n- `SAME_THREAD` : 기본값, 부모 스레드와 동일한 스레드에서 실행, `@BeforeAll`, `@AfterAll`과 동일한 스레드\n- `CONCURRENT` : 별도의 스레드에서 실행\n\n\u003cdetails\u003e\n    \u003csummary\u003eDemo\u003c/summary\u003e\n\n```java\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.parallel.Execution;\nimport org.junit.jupiter.api.parallel.ExecutionMode;\nimport org.junit.jupiter.api.parallel.ResourceLock;\n\nimport java.util.Properties;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.parallel.ResourceAccessMode.READ;\nimport static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE;\nimport static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES;\n\n@Execution(ExecutionMode.CONCURRENT)\npublic class SharedResourcesDemo {\n\n    private Properties backup;\n\n    @BeforeEach\n    void backup() {\n        backup = new Properties();\n        backup.putAll(System.getProperties());\n    }\n\n    @AfterEach\n    void restore() {\n        System.setProperties(backup);\n    }\n\n\n    @Test\n    @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ)\n    void customPropertyIsNotSetByDefault() {\n        assertNull(System.getProperty(\"my.prop\"));\n    }\n\n    @Test\n    @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)\n    void canSetCustomPropertyToApple() {\n        System.setProperty(\"my.prop\", \"apple\");\n        assertEquals(\"apple\", System.getProperty(\"my.prop\"));\n    }\n\n    @Test\n    @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)\n    void canSetCustomPropertyToBanana() {\n        System.setProperty(\"my.prop\", \"banana\");\n        assertEquals(\"banana\", System.getProperty(\"my.prop\"));\n    }\n}\n\n```\n\n\u003c/details\u003e\n\n--- \n\n## stack\n\n- JAVA Application\n- java 17\n- junit 5.8.1\n    - `junit-jupiter-5.9.1.jar`\n    - `junit-jupiter-api-5.9.1.jar`\n    - `junit-jupiter-params-5.9.1.jar`\n    - `junit-platform-commons-1.9.1.jar`\n    - `junit-jupiter-engine-5.9.1.jar`\n    - `junit-platform-engine-1.9.1.jar`\n\n## reference\n\n- [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/#overview)\n- JUnit IN ACTION \u003csub\u003ewritten Cătălin Tudose\u003c/sub\u003e\n\n\u003cimg src=\"img.png\"  width=\"30%\"/\u003e\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgihyeon6394%2Fjunit5-action","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgihyeon6394%2Fjunit5-action","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgihyeon6394%2Fjunit5-action/lists"}