目次
JUnit 5 のユーザーズガイドを読みました。 公式ドキュメントのうち、重要そうなところをピックアップしてまとめています。 そのため公式ドキュメントにあってこちらにない文章もあります。
この記事は からの続きです。
3 テストを記述する
1 2 3 4 5 6 7 8 9 |
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test internal class FirstJUnit5Tests { @Test fun `my first test`() { assertEquals(2, 1 + 1) } } |
1 2 3 4 5 6 7 8 9 10 |
import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; class FirstJUnit5Tests { @Test void myFirstTest() { assertEquals(2, 1 + 1); } } |
3.1 アノテーション
JUnit Jupiter では次のアノテーションを使って、 テストの設定や、フレームワークの拡張ができます。
すべてのコアアノテーションは junit-jupiter-api
の org.junit.jupiter.api
パッケージ にあります。
アノテーション | 説明 |
---|---|
@Test |
関数がテスト用の関数であることを示します。 JUnit 4 の @Test アノテーション とは異なり、 this annotation does not declare any attributes, since test extensions in JUnit Jupiter operate based on their own dedicated annotations. オーバライドされない限り Such methods are inherited unless they are overridden. |
@ParameterizedTest |
メソッドがパラメタライズドテストであることを示します。 Such methods are inherited unless they are overridden. |
@RepeatedTest |
メソッドが Denotes that a method is a test template for a repeated test. Such methods are inherited unless they are overridden. |
@TestFactory |
Denotes that a method is a test factory for dynamic tests. Such methods are inherited unless they are overridden. |
@TestInstance |
Used to configure the test instance lifecycle for the annotated test class. Such annotations are inherited. |
@TestTemplate |
Denotes that a method is a template for test cases designed to be invoked multiple times depending on the number of invocation contexts returned by the registered providers. Such methods are inherited unless they are overridden. |
@DisplayName |
テストクラスおよびテストメソッドに、独自の表示名を設定します。 Such annotations are not inherited. |
@BeforeEach |
このアノテーションが付与されたメソッドは @Test または @RepeatedTest , @ParameterizedTest , @TestFactory でアノテートされたそれぞれのメソッドが実行される前に実行されます。 JUnit 4 での @Before に相当します。 Such methods are inherited unless they are overridden. |
@AfterEach |
このアノテーションが付与されたメソッドは @Test または @ParameterizedTest , @TestFactory でアノテートされたそれぞれのメソッドが実行された後に実行されます。 JUnit 4 での @After に相当します。 Such methods are inherited unless they are overridden. |
@BeforeAll |
Denotes that the annotated method should be executed before all @Test, @RepeatedTest, @ParameterizedTest, and @TestFactory methods in the current class; analogous to JUnit 4’s @BeforeClass. Such methods are inherited (unless they are hidden or overridden) and must be static (unless the “per-class” test instance lifecycle is used). |
@AfterAll |
Denotes that the annotated method should be executed after all @Test, @RepeatedTest, @ParameterizedTest, and @TestFactory methods in the current class; analogous to JUnit 4’s @AfterClass. Such methods are inherited (unless they are hidden or overridden) and must be static (unless the “per-class” test instance lifecycle is used). |
@Nested |
Denotes that the annotated class is a nested, non-static test class. @BeforeAll and @AfterAll methods cannot be used directly in a @Nested test class unless the “per-class” test instance lifecycle is used. Such annotations are not inherited. |
@Tag |
Used to declare tags for filtering tests, either at the class or method level; JUnit 4 でいうところの TestNG , Categories に相当します。 analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are inherited at the class level but not at the method level.
|
@Disabled |
テストクラス、テストメソッドを無効にする場合に使います。 JUnit 4 でいうところの @Ignore に相当します。 Such annotations are not inherited. |
@ExtendWith |
カスタム拡張を登録する場合に使います。 Used to register custom extensions. Such annotations are inherited. |
3.1.1 メタアノテーションと合成アノテーション
JUnit Jupiter のアノテーションはメタアノテーションとして扱われるため、 JUnit Jupiter アノテーション のセマンティクスを継承した合成アノテーションを作成できます。
例えば @Tag("fast")
をコピー&ペーストする代わりに、 次のように独自の合成アノテーション @Fast
を作成して利用できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy import org.junit.jupiter.api.Tag @Target( AnnotationTarget.CLASS, AnnotationTarget.FILE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER ) @Retention(RetentionPolicy.RUNTIME) @Tag("fast") annotation class Fast |
1 2 3 4 5 6 7 8 9 10 11 12 |
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Tag; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Tag("fast") public @interface Fast { } |
3.2 テストクラスとメソッド
テストメソッド は次のアノテーションで直接またはメタアノテートされたインスタンスメソッドです。
@Test
@RepeatedTest
@ParameterizedTest
@TestFactory
@TestTemplate
トップレベルのクラスまたは静的メンバクラスで、少なくともひとつのテストメソッドを持っているものをテストクラスといいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import 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 internal class StandardTests { @BeforeEach fun init() { } @Test fun `succeeding test`() { } @Test fun `failing test`() { fail<Any>("a failing test") } @Test @Disabled("for demonstration purposes") fun `skipped test`() { // not executed } @AfterEach fun tearDown() { } companion object { @BeforeAll fun initAll() { } @AfterAll fun tearDownAll() { } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
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() { } } |
テストクラスもテストメソッドも、 public
にする必要はありません。
3.3 表示名
テストクラスもテストメソッドも、独自に表示名を決めることができます。 スペース、特殊文字、絵文字も使用可能です。 その名前はテストランナーおよびテストレポートで表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test @DisplayName("A special test case") internal class DisplayNameDemo { @Test @DisplayName("Custom test name containing spaces") fun testWithDisplayNameContainingSpaces() { } @Test @DisplayName("╯°□°)╯") fun testWithDisplayNameContainingSpecialCharacters() { } @Test @DisplayName("?") fun testWithDisplayNameContainingEmoji() { } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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() { } } |
IntelliJ IDEA のツールウィンドウでも表示されます。
ただ、 Gradle のコマンド ./gradlew test
でテストを実行した場合、 Gradle の出力するレポートで絵文字は ?? として表示されます。
3.4 アサーション
JUnit Jupiter のアサーションには、 JUnit 4 にあったアサーションの多くと、 Java 8 のラムダと連携可能ないくつかのアサーションがあります。 すべてのアサーションは org.junit.jupiter.api.Assertions
クラス の静的メソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
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('a' < 'b', () -> "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!"; } } |
JUnit Jupiter は Kotlin とうまく連携するアサーションも備えています。 すべての JUnit Jupiter Kotlin アサーション は org.junit.jupiter.api
パッケージ のトップレベル関数です。 たとえば assertAll
は、 Java のコードでは org.junit.jupiter.api.Assertions.assertAll
を使っていましたが、 Kotlin では Kotlin で使いやすいようにカスタマイズされている org.junit.jupiter.api.assertAll
を使います。 もちろん、 org.junit.jupiter.api.Assertions.assertAll
も利用可能です。 (関連: Qiita記事: JUnit 5 assertAll)
次のサンプルコードで、下の2つのテストメソッドは、 Kotlin のサンプルにのみあるテストメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
import java.time.Duration.ofMillis import java.time.Duration.ofMinutes import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.Assertions.assertTimeout import org.junit.jupiter.api.Assertions.assertTimeoutPreemptively import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.function.ThrowingSupplier internal class AssertionsDemo { companion object { data class Person( val firstName: String, val lastName: String ) val person = Person( "John", "Doe" ) val people = listOf(person, person) } @Test fun `standard assertions`() { assertEquals(2, 2) assertEquals( 4, 4, "The optional assertion message " + "is now the last parameter." ) assertTrue('a' < 'b') { "Assertion messages can be lazily evaluated -- " + "to avoid constructing complex messages " + "unnecessarily." } } @Test fun `grouped assertions`() { // In a grouped assertion all assertions are executed, // and any failures will be reported together. assertAll("person", { assertEquals("John", person.firstName) }, { assertEquals("Doe", person.lastName) } ) } @Test fun `dependent assertions`() { // Within a code block, if an assertion fails the // subsequent code in the same block will be skipped. assertAll("properties", { val firstName = person.firstName 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. val lastName = person.lastName assertNotNull(lastName) // Executed // only if the previous assertion is valid. assertAll("last name", { assertTrue(lastName.startsWith("D")) }, { assertTrue(lastName.endsWith("e")) } ) } ) } @Test fun `exception testing`() { val exception = assertThrows<IllegalArgumentException>( "Should throw an exception" ) { throw IllegalArgumentException("a message") } assertEquals("a message", exception.message) } @Test fun `timeout not exceeded`() { // The following assertion succeeds. assertTimeout(ofMinutes(2)) { // Perform task that takes less than 2 minutes. } } @Test fun `timeout not exceeded with result`() { // The following assertion succeeds, // and returns the supplied object. val actualResult = assertTimeout<String>( ofMinutes(2) ) { "a result" } assertEquals("a result", actualResult) } @Test fun `timeout not exceeded with method`() { // The following assertion invokes // a method reference and returns an object. val actualGreeting = assertTimeout<String>( ofMinutes(2), ThrowingSupplier<String> { greeting() }) assertEquals("Hello, World!", actualGreeting) } @Test fun `timeout exceeded`() { // 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 fun `timeout exceeded with preemptive termination`() { // 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) } } @Test fun `assertions from a stream`() { assertAll( "people with name starting with J", people .stream() .map { // This mapping returns Stream<() -> Unit> { assertTrue( it.firstName.startsWith("J")) } } ) } @Test fun `assertions from a collection`() { assertAll( "people with last name of Doe", people.map { { assertEquals("Doe", it.lastName) } } ) } private fun `greeting`(): String { return "Hello, World!" } } |