1. 概要

このドキュメントの目的は、テストを書くプログラマや機能を拡張する開発者(extension authors)、駆動部分の開発者(engine authors)ならびにビルドツールやIDEのベンダーのために、包括的な参考資料を提供することです。

このドキュメントはPDFでも利用可能です。

💡 翻訳 このドキュメントは簡体字中国語日本語でも利用可能です。

1.1. JUnit 5とは?

これまでのバージョンのJUnitとは異なり、JUnit 5は3つのサブプロジェクトで開発されている様々なモジュールから構成されています。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platformは、JVM上でテスティングフレームワークを起動させるための基礎としての機能を果たします。また、プラットフォーム上で動作するテスティングフレームワークを開発するためのTestEngine APIを定義しています。さらに、プラットフォーム上であらゆるTestEngineを実行するために、JUnit 4ベースのRuunnerと同様に、コマンドラインからプラットフォームを立ち上たり、GradleMaven向けプラグインを構築するためのコンソールラウンチャーを提供します。

JUnit Jupiterは、JUnit 5でテストを書いたり拡張するための新しいプログラミングモデル拡張モデルの組み合わせです。Jupiterのサブプロジェクトは、プラットフォーム上でJupiterベースのテストを実行するためのTestEngineを提供します。

JUnit Vintageは、プラットフォーム上でJUnit3とJUnit4ベースのテストを実行するためのTestEngineを提供します。

1.2. サポートしているJavaのバージョン

JUnit 5は実行時にJava 8(またはそれ以上)を必要とします。しかしながら、それよりも前のバージョンのJDKでコンパイルされたコードもテスト可能です。

1.3. ヘルプ

JUnit 5に関連した質問はStack Overflowに投稿するか、Gitterでチャットしてください。

2. インストール

最終的なリリースやマイルストーンのためのアーティファクトは、Maven Centralにデプロイされます。

スナップショット版はSonatypeのスナップショットレポジトリ/org/junit以下にデプロイされます。

2.1. 依存関係のメタデータ

2.1.1 JUnit Platform

2.1.2. JUnit Jupiter

2.1.3. JUnit Vintage

2.1.4. 部品表

次のMaven座標以下で提供されている 部品表 POMは、MavenGradleを用いて複数の上記アーティファクトを参照する際に、依存関係管理を容易にするために利用できます。

2.1.5. 依存関係

上記のアーティファクトは全て、次の @API Guardian JARで公開されているMaven POMsに依存関係があります。 - Group ID: org.apiguardian - Articfact ID: apiguardian-api - Version: 1.0.0

さらに、上記アーティファクトのほとんどは、次の OpenTest4J JARと直接的、または推移的な依存関係があります。

2.2. 依存関係図

2.3. JUnit Jupiter サンプルプロジェクト

junit5-samplesレポジトリは、JUnit JupiterとJUnit Vintageをベースとしたサンプルプロジェクト集をホストしています。下記プロジェクトにはbuild.gradlepom.xmlがそれぞれ置かれています。

3. テストを書く

最初のテストケース

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と異なり、このアノテーションはいかなる属性も宣言しません。これは、JUnit Jupiterにおけるテスト拡張が専用のアノテーションを元に作動するからです。メソッドはオーバーライドされない限り、継承されます。
@ParameterizedTest パラメータ化テストであることを意味します。メソッドはオーバーライドされない限り、継承されます。
@RepeatedTest 繰り返しテストのテンプレートメソッドであることを意味します。メソッドはオーバーライドされない限り、継承されます。
@TestFactory 動的テストのファクトリーメソッドであることを意味します。メソッドはオーバーライドされない限り、継承されます。
@TestInstance アノテーションが付与されたテストクラスのテストインスタンス・ライフサイクルを設定するために使用されます。アノテーションは継承されます。
@TestTemplate テストケースのテンプレートメソッドであることを意味します。テンプレートは登録されたプロバイダによって返される呼び出しコンテキストの数に応じて複数回呼び出されます。メソッドはオーバーライドされない限り、継承されます。
@DisplayName テストクラスもしくはテストメソッドのカスタム表示名を宣言します。アノテーションは継承されません。
@BeforeEach 現在のクラス内にある各テスト@Test, @RepeatedTest, @ParameterizedTest, @TestFactory)が実行されるbefore)に実行されるメソッドを意味します。JUnit 4の@Beforeと類似したものです。メソッドはオーバーライドされない限り、継承されます。
@AfterEach 現在のクラス内にある各テスト@Test, @RepeatedTest, @ParameterizedTest, @TestFactory)が実行されたafter)に実行されるメソッドを意味します。JUnit 4の@Afterと類似したものです。メソッドはオーバーライドされない限り、継承されます。
@BeforeAll 現在のクラス内にある全テスト@Test, @RepeatedTest, @ParameterizedTest, @TestFactory)が実行されるbefore)に実行されるメソッドを意味します。JUnit 4の@BeforeClassと類似したものです。メソッドは隠蔽またはオーバーライドされない限り、継承され、("クラス毎に"テストインスタンス・ライフサイクルを使わない限り)staticでないといけません。
@AfterAll 現在のクラス内にある全テスト@Test, @RepeatedTest, @ParameterizedTest, @TestFactory)が実行されたafter)に実行されるメソッドを意味します。JUnit 4の@AfterClassと類似したものです。メソッドは隠蔽またはオーバーライドされない限り、継承され、("クラス毎に"テストインスタンス・ライフサイクルを使わない限り)staticでないといけません。
@Nested ネストされた非staticなテストクラスであることを意味します。@BeforeAll@AfterAllメソッドは、"クラス毎に"テストインスタンス・ライフサイクルを使わない限り、@Nestedテストクラスの中では直接使うことができません。メソッドは継承されません。
@Tag クラスもしくはメソッドレベルでテストをフィルタリングするためのタグを宣言できます。TestNGのtest groupsもしくはJUnit 4のCategoriesと類似したものです。アノテーションはクラスレベルでは継承されますが、メソッドレベルでは継承されません。
@Disabled テストクラスもしくはテストメソッドを無効化できます。JUnit 4の@Ignoreと類似したものです。アノテーションは継承されません。
@ExtendWith カスタム拡張を登録できます。アノテーションは継承されます。

次のアノテーションをつけたメソッドは値を返してはいけません。 (@Test, @TestTemplate, @RepeatedTest, @BeforeAll, @AfterAll, @BeforeEach, @AfterEach)

⚠️ 注意点:いくつかのアノテーションは現在実験段階である恐れがあります。詳細に関しては、実験的なAPIsをご覧ください。

3.1.1. メタアノテーションと合成アノテーション

JUnit Jupiterアノテーションはメタアノテーションとして使うことができます。つまり、メタアノテーションのセマンティックを自動で継承する独自の合成アノテーションを定義できます。

例えば、コードベースに@Tag("fast")タグ付けとフィルタリングをご覧ください。)をコピー&ペーストする代わりに、次のように@Fastというカスタム合成アノテーションを作成できます。@Fast@Tag("fast")の代替として利用できます。

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@ParamterizedTest@TsetFactory@TestTemplateが付与されたインスタンスメソッドです。テストクラスとは、少なくとも1つのテストメソッドを含むトップレベルまたは静的なメンバークラスです。

標準的なテストケース

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. 表示名

テストクラスとテストメソッドはカスタム表示名(スペースや特殊文字、絵文字さえ使用可能) を宣言できます。それらがテストランナーとテストレポートによって表示されます。

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() {
    }

}

3.4. アサーション

JUnit Jupiterには、JUnit 4のアサーションメソッドの多くを備えています。また、いくつかはJava 8のラムダ式で使うことができます。全てのJUnit Jupiterアサーションは、org.junit.jupiter.api.Assertionsクラスのstaticメソッドです。

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パッケージのトップレベル関数です。

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.assertThrows

class AssertionsKotlinDemo {

    @Test
    fun `grouped assertions`() {
        assertAll("person",
            { assertEquals("John", person.firstName) },
            { assertEquals("Doe", person.lastName) }
        )
    }

    @Test
    fun `exception testing`() {
        val exception = assertThrows<IllegalArgumentException> ("Should throw an exception") {
            throw IllegalArgumentException("a message")
        }
        assertEquals("a message", exception.message)
    }

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

}

3.4.1. サードパーティのアサーションライブラリ

JUnit Jupiterによって提供されているアサーション機能は多くのテストシナリオで十分ですが、matchersといったより強力で追加的な機能が求められたり必要な場合があります。そのような場合、JUnitチームは、AssertJHamcrestTruthなどといったサードパーティのアサーションライブラリの使用をお薦めします。したがって、開発者は自由に選んだアサーションライブラリを使うことができます。

例えば、matchersと流暢なAPI(fluent API)の組み合わせは、アサーションをよりわかりやすく、読みやすくするために使うことができます。しかしながら、JUnit Jupiterのorg.junit.jupiter.Assertionsクラスは、HamcrestのMatcherを許容しているJUnit 4のorg.junit.jupiter.AssertクラスにあるようなassertThat()メソッドを提供していません。代わりに、開発者はサードパーティのアサーションライブラリによって提供されているマッチャー用の組み込みサポートを使うことが奨励されています。

次の例は、JUnit JupiterのテストにおいてHamcrestからassertThat()のサポートを使う方法を説明しています。Hamcrestライブラリがクラスパスに加えられている限り、assertThat()is()equalTo()といったメソッドを静的にインポートできます。また、それらをテストの中で、下に示すassertWithHamcrestMatcher()のように使うことができます。

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;

class HamcrestAssertionDemo {

    @Test
    void assertWithHamcrestMatcher() {
        assertThat(2 + 1, is(equalTo(3)));
    }

}

当然、JUnit 4のプログラミングモデルに基づいたレガシーテストもorg.junit.Assert#assertThatを用いて継続して利用可能です。

3.5. アサンプション

JUnit Jupiterには、JUnit 4のアサンプションメソッドのサブセットを備えています。また、いくつかはJava 8のラムダ式で使うことができます。全てのJUnit Jupiterアサンプションは、org.junit.jupiter.api.Assumptionsクラスのstaticメソッドです。

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");
    }

}

3.6. テストの無効化

テストクラス全体もしくは各テストメソッドは、@Disabledアノテーションまたは条件付きテスト実行で議論されているアノテーションの1つ、カスタムExecutionConditionによって無効化できます。

これは@Disabledテストクラスです。

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

@Disabled
class DisabledClassDemo {
    @Test
    void testWillBeSkipped() {
    }
}

そして、これは@Disabledテストメソッドを含むテストクラスです。

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class DisabledTestsDemo {

    @Disabled
    @Test
    void testWillBeSkipped() {
    }

    @Test
    void testWillBeExecuted() {
    }
}

3.7. 条件付きテスト実行

JUnit JupiterのExecutionCondition拡張APIを用いて、ある条件に基づいたコンテナまたはテストをプログラム的に 有効または無効にできます。そのような条件の最も単純な例は、@Disabledをサポートしている組み込みのDisabledConditionです(テストの無効化をご覧ください)。@Disabledに加えて、JUnit Jupiterは、org.junit.jupiter.api.conditionパッケージに他のいくつかのアノテーションベースの条件もサポートしており、コンテナやテストを宣言的に 有効または無効にできます。詳細については、次章をご覧ください。

💡 合成アノテーション:次章に列挙する条件アノテーションはいずれも、カスタム合成アノテーションを作るためにメタアノテーションとしても使える可能性があります。例えば、[@EnabledOnOsのデモ]()にある@TestOnMacアノテーションは、@Test@EnableOnOsを単一で再利用可能なアノテーションに組み合わせる方法を示しています。

⚠️ 次章に列挙する条件アノテーションはそれぞれ、テストインターフェイスまたはテストクラス、テストメソッドに一度だけ宣言できます。もし条件アノテーションがある要素に直接的か間接的、またはメタ的に複数存在する場合、JUnitによって発見された最初のアノテーションのみ使われます(いかなる追加的なアノテーションも静かに無視されます)。しかしながら、org.junit.jupiter.api.conditionパッケージでは、各条件アノテーションは他の条件アノテーションと共に使われる可能性があります。

3.7.1. オペレーティングシステムに関する条件

@EnabledOnOs@DisabledOnOsアノテーションを使うことで、特定のオペーティングシステム上でコンテナまたはテストを有効にしたり無効にできます。

@Test
@EnabledOnOs(MAC)
void onlyOnMacOs() {
    // ...
}

@TestOnMac
void testOnMac() {
    // ...
}

@Test
@EnabledOnOs({ LINUX, MAC })
void onLinuxOrMac() {
    // ...
}

@Test
@DisabledOnOs(WINDOWS)
void notOnWindows() {
    // ...
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@EnabledOnOs(MAC)
@interface TestOnMac {
}

3.7.2. Java実行環境に関する条件

@EnabledOnJre@DisabledOnJreアノテーションを使うことで、特定のバージョンのJava実行環境(JRE)上でコンテナまたはテストを有効にしたり無効にできます。

@Test
@EnabledOnJre(JAVA_8)
void onlyOnJava8() {
    // ...
}

@Test
@EnabledOnJre({ JAVA_9, JAVA_10 })
void onJava9Or10() {
    // ...
}

@Test
@DisabledOnJre(JAVA_9)
void notOnJava9() {
    // ...
}

3.7.3. システムプロパティに関する条件

@EnabledIfSystemProperty@DisabledIfSystemPropertyアノテーションを使うことで、namedで指定したJVMシステムプロパティの値に応じて、コンテナまたはテストを有効にしたり無効にできます。matches属性を使うことで、値は正規表現として解釈されます。

@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
void onlyOn64BitArchitectures() {
    // ...
}

@Test
@DisabledIfSystemProperty(named = "ci-server", matches = "true")
void notOnCiServer() {
    // ...
}

3.7.4. 環境変数に関する条件

@EnabledIfEnvironmentVariable@DisabledIfEnvironmentVariableアノテーションを使うことで、基礎となるオペレーティングシステムからのnamedで指定した環境変数の値に応じて、コンテナまたはテストを有効にしたり無効にできます。matches属性を使うことで、値は正規表現として解釈されます。

@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server")
void onlyOnStagingServer() {
    // ...
}

@Test
@DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")
void notOnDeveloperWorkstation() {
    // ...
}

3.7.5. スクリプトベースの条件

JUnit Jupiterは、@EnabledIf@DisabledIfアノテーションを使うことで、設定されたスクリプトの評価値に応じて、コンテナまたはテストを有効にしたり無効にできる機能を提供しています。スクリプトは、JavaScriptまたはGroovy、JSR 223で定義されているJava Scripting APIをサポートしているスクリプト言語であれば記述できます。

⚠️ @EnabledIf@DisabledIfを使った条件付きテストテスト実行は、現在実験的な機能です。詳細については、実験的な APIsをご覧ください。

💡 スクリプトのロジックが、現オペレーティングまたは現Java実行環境のバージョン、特定のJVMシステムプロパティ、特定の環境変数にのみ依存している場合、その目的に合った組み込みのアノテーションを使うことを考慮すべきです。さらなる詳細については、前章をご覧ください。

ℹ️ 同じスクリプトベースの条件を多数使っている場合、より速く、型安全で、メンテナンスのしやすい方法で条件を実装するために、それに合った実行条件拡張を書くことを考えてみてください。

@Test // Static JavaScript expression.
@EnabledIf("2 * 3 == 6")
void willBeExecuted() {
    // ...
}

@RepeatedTest(10) // Dynamic JavaScript expression.
@DisabledIf("Math.random() < 0.314159")
void mightNotBeExecuted() {
    // ...
}

@Test // Regular expression testing bound system property.
@DisabledIf("/32/.test(systemProperty.get('os.arch'))")
void disabledOn32BitArchitectures() {
    assertFalse(System.getProperty("os.arch").contains("32"));
}

@Test
@EnabledIf("'CI' == systemEnvironment.get('ENV')")
void onlyOnCiServer() {
    assertTrue("CI".equals(System.getenv("ENV")));
}

@Test // Multi-line script, custom engine name and custom reason.
@EnabledIf(value = {
                "load('nashorn:mozilla_compat.js')",
                "importPackage(java.time)",
                "",
                "var today = LocalDate.now()",
                "var tomorrow = today.plusDays(1)",
                "tomorrow.isAfter(today)"
            },
            engine = "nashorn",
            reason = "Self-fulfilling: {result}")
void theDayAfterTomorrow() {
    LocalDate today = LocalDate.now();
    LocalDate tomorrow = today.plusDays(1);
    assertTrue(tomorrow.isAfter(today));
}

スクリプトバインディング

次の名前は、各スクリプトコンテキストでバインドされているため、スクリプト内で使用可能です。accessorは、単純な String get(String name)メソッドを介してマップライク(map-like)な構造へのアクセスを提供します。

Name Type Description
systemEnvironment accessor オペレーティングシステム環境変数のアクセサ
systemProperty accessor JVMシステムプロパティのアクセサ
junitConfigurationParameter accessor 設定パラメータのアクセサ
junitDisplayName String テストまたはコンテナの表示名
junitTags Set<String> テストまたはコンテナに振られている全てのタグ
junitUniqueId String テストまたはコンテナのユニークなID

3.8. タグとフィルタリング

テストクラスとメソッドは@Tagアノテーションを用いてタグ付けできます。それらのタグは後にテスト発見と実行をフィルタリングするために使われます。

3.8.1. タグの構文規則

ℹ️ 上の文章で、トリミングされたというのは、語頭と語尾の空白文字を取り除いたということを意味します。

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag("fast")
@Tag("model")
class TaggingDemo {

    @Test
    @Tag("taxes")
    void testingTaxCalculation() {
    }

}

3.9. テストインスタンス・ライフサイクル

各テストメソッドの独立した実行と、変化可能なテストインスタンスの状態による予期せぬ副作用を避けるため、JUnitは各テストメソッドを実行する前に、各テストクラスの新しいインスタンスを生成します(テストクラスとメソッドをご覧ください)。この"メソッドごと"のテストインスタンス・ライフサイクルはJUnit Jupiterではデフォルトの動作で、以前の全てのバージョンのJUnitと類似したものになっています。

ℹ️ @Disabled@DisabledOnOsといった条件によって無効化されたテストメソッドであっても、テストクラスはインスタンス化されることに注意してください。これは"メソッドごと"テストインスタンス・ライフサイクルモードが有効である時でも同様です。

JUnit Jupiterに全テストメソッドを同じテストインスタンス上で実行してほしい場合は、単にテストクラスに@TestInstance(Lifecycle.PER_CLASS)アノテーションを付与するだけで実現可能です。このモードを使用する場合、テストクラス毎に新しいテストインスタンスが一度だけ生成されます。これによって、テストメソッドがインスタンス変数に保存された状態に依存する場合は、@BeforeEachまたはAfterEachメソッドでその状態をリセットする必要かもしれません。

”クラスごと”のモードは、デフォルトの"メソッドごと"モードに比べていくつかの追加的な利点があります。特に、"クラスごと"モードを使うと、インターフェイスのdefaultメソッドと同様に、@BeforeAll@AfterAllメソッドを非静的メソッドとして宣言することが可能になります。そのため、"クラスごと"モードでは、@Nestedテストクラス内で@BeforeAll@AfterAllメソッドを使うことができます。

Kotlinプログラミング言語でテストを書いている場合、”クラスごと”テストインスタンス・ライフサイクルモードに切り替えることで、@BeforeAll@AfterAllメソッドの実装がより容易になるかもしれません。

3.9.1. デフォルトのテストインスタンス・ライフサイクルの変更

テストクラスまたはテストインターフェイスに@TestInstanceが付与されていない場合、JUnit Jupiterはデフォルトのライフサイクルモードを使います。 標準的なデフォルトモードはPER_METHODですが、テスト計画全体を実行するためのデフォルトを変更することが可能です。デフォルトのテストインスタンス・ライフサイクルモードを変更するには、単にjunit.jupiter.testinstance.lifecycle.default設定パラメータTestInstance.Lifecycleに定義されているenum定数名を(大文字・小文字を無視して)設定するだけです。これは、JVMシステムプロパティとして渡すか、Launcherに渡されるLauncherDiscoveryRequest内の設定パラメータとして渡すか、JUnit Platformの設定ファイル(詳細については、設定パラメータをご覧ください。)を通して渡します。

例えば、デフォルトのテストインスタンス・ライフサイクルモードをLifeCycle.PER_CLASSに設定するには、JVMを次のシステムプロパティで起動してください。

-Djunit.jupiter.testinstance.lifecycle.default=per_class

しかしながら、JUnit Platformの設定ファイルを通してデフォルトのテストインスタンス・ライフサイクルモードを設定する方が、より堅牢なソリューションです。設定ファイルはプロジェクトのバージョン管理システムに取り込め、自身のIDEやビルドソフトウェアで利用できます。

JUnit Platformの設定ファイルを通してデフォルトのテストインスタンス・ライフサイクルモードを設定ためには、次の内容を含んだjunit-platform.propertiesという名前のファイルをクラスパス(例えば、src/test/resources)のルートに生成してください。

junit.jupiter.testinstance.lifecycle.default = per_class

⚠️ デフォルトのテストインスタンス・ライフサイクルモードを変更することは、一貫性を持って適用しないと、予測不可能な結果と壊れやすいビルドにつながる恐れがあります。例えば、ビルドではデフォルトとして”クラスごと”のセマンティックを設定していながら、IDEでのテストでは"メソッドごと"で実行していた場合、ビルドサーバで起きるエラーをデバッグすることは困難になる恐れがあります。そのため、JVMシステムプロパティの代わりに、JUnit Platformの設定ファイルを使ってデフォルトを変更することをお薦めします。

3.10. ネストされたテスト

ネストされたテストは、テスト開発者が様々なグループのテスト間の関係を表現することを可能にします。これがその美しい例です。

スタックをテストするためのネストされたテスト

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());
            }
        }
    }
}

ℹ️ 非静的なネストされたクラス(つまり、内部クラス)のみ@Nestedテストクラスとなります。ネストは任意に深くすることができ、それら内部クラスは一つの例外を除いて、テストクラスの完全なメンバーとして考えられます。例外は@BeforeAll@AfterAllで、これらはデフォルトでは動作しません。その理由は、Javaが内部クラスにstaticなメンバーを許さないためです。しかしながら、この制限は@Nestedテストクラスに@TestInstance(Lifecycle.PER_CLASS)を付与することで回避できます(テストインスタンス・ライフサイクルをご覧ください)。

3.11. コンストラクタとメソッドへの依存性注入

JUnitの前バージョン全てにおいて、テストコンストラクタまたはメソッドは(少なくとも標準的なRunner実装を用いる場合は)パラメータを持つことが許されていませんでした。JUnit Jupiterでの大きな変更の1つとして、テストコンストラクタとメソッドどちらもパラメータを持てるようになりました。このことは、大きな柔軟性をもたらし、コンストラクタとメソッドに依存性の注入が可能になりました。

ParameterResolverは、実行時に動的にパラメータを解決することを望むテスト拡張のためのAPIを定義しています。テストコンストラクタまたは@Test@TestFactory@BeforeEach@AfterEach@BeforeAll@AfterAllメソッドがパラメータを許容する場合は、そのパラメータは登録されたParameterResolverによって実行時に解決されなければなりません。

現在は、3つの組み込みリゾルバが自動的に登録されます。

TestInfoは、JUnit 4のTestName規則の代替として動作します。次のコードは、テストコンストラクタと@BeforeEachメソッド、@TestメソッドにTestInfoを注入させる方法を示しています。

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);
    }

}

ℹ️ 他のパラメータリゾルバは、@ExtendWithを用いた適切な拡張を登録することによって明示的に有効化する必要があります。

カスタムParameterResolverの例に関してRandomParametersExtensionを確認しましょう。リリース可能なものではありませんが、拡張モデルとパラメータ解決プロセス両方の単純さと表現性を例示しています。MyRandomParametersTestは、@Testメソッドへのランダム値の挿入方法をを示しています。

@ExtendWith(RandomParametersExtension.class)
class MyRandomParametersTest {

    @Test
    void injectsInteger(@Random int i, @Random int j) {
        assertNotEquals(i, j);
    }

    @Test
    void injectsDouble(@Random double d) {
        assertEquals(0.0, d, 1.0);
    }

}

現実的なユースケースとして、MockitoExtensionSpringExtensionのソースコードを確認してください。

3.12. テストインターフェイスとデフォルトメソッド

JUnit Jupiterは、@Test@RepeatedTest@ParameterizedTest@TestFactory@TestTemplate@BeforeEach@AfterEachにインターフェイスのdefaultメソッドを宣言できるようにしています。@BeforeAll@AfrterAllはテストインターフェイス内でstaticメソッドを宣言するか、もしテストインターフェイスまたはテストクラスに@TestInstance(Lifecycle.PER_CLASS)が付与されている場合はインターフェイスdefaultメソッドを宣言することができます(テストインスタンス・ライフサイクルをご覧ください)。いくつかの例を示します。

@TestInstance(Lifecycle.PER_CLASS)
interface TestLifecycleLogger {

    static final Logger LOG = Logger.getLogger(TestLifecycleLogger.class.getName());

    @BeforeAll
    default void beforeAllTests() {
        LOG.info("Before all tests");
    }

    @AfterAll
    default void afterAllTests() {
        LOG.info("After all tests");
    }

    @BeforeEach
    default void beforeEachTest(TestInfo testInfo) {
        LOG.info(() -> String.format("About to execute [%s]",
            testInfo.getDisplayName()));
    }

    @AfterEach
    default void afterEachTest(TestInfo testInfo) {
        LOG.info(() -> String.format("Finished executing [%s]",
            testInfo.getDisplayName()));
    }

}
interface TestInterfaceDynamicTestsDemo {

    @TestFactory
    default Collection<DynamicTest> dynamicTestsFromCollection() {
        return Arrays.asList(
            dynamicTest("1st dynamic test in test interface", () -> assertTrue(true)),
            dynamicTest("2nd dynamic test in test interface", () -> assertEquals(4, 2 * 2))
        );
    }

}

@ExtenWith@Tagはテストインターフェイスとして宣言することができるため、インターフェイスを実装したクラスは自動的にタグと拡張を継承します。TimingExtensionのソースコードを見るには、BeforeとAfterのテスト実行コールバックをご覧ください。

@Tag("timed")
@ExtendWith(TimingExtension.class)
interface TimeExecutionLogger {
}

テストクラスでは、これらのテストインターフェイスを実装することで適用することができます。

class TestInterfaceDemo implements TestLifecycleLogger,
        TimeExecutionLogger, TestInterfaceDynamicTestsDemo {

    @Test
    void isEqualValue() {
        assertEquals(1, 1, "is always equal");
    }

}

TestInterfaceDemoを実行すると、次と同様の出力が得られます。

:junitPlatformTest
INFO  example.TestLifecycleLogger - Before all tests
INFO  example.TestLifecycleLogger - About to execute [dynamicTestsFromCollection()]
INFO  example.TimingExtension - Method [dynamicTestsFromCollection] took 13 ms.
INFO  example.TestLifecycleLogger - Finished executing [dynamicTestsFromCollection()]
INFO  example.TestLifecycleLogger - About to execute [isEqualValue()]
INFO  example.TimingExtension - Method [isEqualValue] took 1 ms.
INFO  example.TestLifecycleLogger - Finished executing [isEqualValue()]
INFO  example.TestLifecycleLogger - After all tests

Test run finished after 190 ms
[         3 containers found      ]
[         0 containers skipped    ]
[         3 containers started    ]
[         0 containers aborted    ]
[         3 containers successful ]
[         0 containers failed     ]
[         3 tests found           ]
[         0 tests skipped         ]
[         3 tests started         ]
[         0 tests aborted         ]
[         3 tests successful      ]
[         0 tests failed          ]

BUILD SUCCESSFUL

この機能の他のあり得る適用としては、インターフェイス契約のためにテストを書くことです。例えば、Object.equalsまたはComparable.compareToの実装がどう振る舞うべきかのテストを、次のように書くことができます。

public interface Testable<T> {

    T createValue();

}
public interface EqualsContract<T> extends Testable<T> {

    T createNotEqualValue();

    @Test
    default void valueEqualsItself() {
        T value = createValue();
        assertEquals(value, value);
    }

    @Test
    default void valueDoesNotEqualNull() {
        T value = createValue();
        assertFalse(value.equals(null));
    }

    @Test
    default void valueDoesNotEqualDifferentValue() {
        T value = createValue();
        T differentValue = createNotEqualValue();
        assertNotEquals(value, differentValue);
        assertNotEquals(differentValue, value);
    }

}
public interface ComparableContract<T extends Comparable<T>> extends Testable<T> {

    T createSmallerValue();

    @Test
    default void returnsZeroWhenComparedToItself() {
        T value = createValue();
        assertEquals(0, value.compareTo(value));
    }

    @Test
    default void returnsPositiveNumberComparedToSmallerValue() {
        T value = createValue();
        T smallerValue = createSmallerValue();
        assertTrue(value.compareTo(smallerValue) > 0);
    }

    @Test
    default void returnsNegativeNumberComparedToSmallerValue() {
        T value = createValue();
        T smallerValue = createSmallerValue();
        assertTrue(smallerValue.compareTo(value) < 0);
    }

}

テストクラスでは、2つの契約インターフェイスを実装することで、対応するテストを継承します。もちろん、抽象メソッドを実装する必要があります。

class StringTests implements ComparableContract<String>, EqualsContract<String> {

    @Override
    public String createValue() {
        return "foo";
    }

    @Override
    public String createSmallerValue() {
        return "bar"; // 'b' < 'f' in "foo"
    }

    @Override
    public String createNotEqualValue() {
        return "baz";
    }

}

ℹ️ 上記のテストは、単に例であって、完全ではありません。

3.13. 繰り返しテスト

JUnit Jupiterは、@RepeatedTestを付与し、繰り返してほしい回数を設定するだけで、特定回数テストを繰り返す機能を提供しています。繰り返しテストの各呼び出しは、通常の@Testメソッドの実行のように振る舞い、同じライフサイクル・コールバックと拡張を完全にサポートしています。

次の例は、自動で10回繰り返すrepeatedTest()という名前のテストの宣言方法を示しています。

@RepeatedTest(10)
void repeatedTest() {
    // ...
}

繰り返し回数の設定に加えて、@RepeatedTestアノテーションのname属性を用いることでカスタム表示名も設定できます。さらに、表示名は、静的なテキストと動的なプレースホルダの組み合わせで構成されるパターンにすることもできます。次のプレースホルダが現在サポートされています。

ある繰り返し回数時点でのデフォルトの表示名は、次のパターンに基づいて生成されます:"repetition {currentRepetition} of {totalRepetitions}"。そのため、先ほどの例の各繰り返し回数における表示名は次のようになります:repetition 1 of 10repetition 2 of 10など。@RepeatedTestメソッドの表示名に各繰り返しの名前を含めたい場合は、独自のカスタムパターンを定義するか、事前定義されたRepeatedTest.LONG_DISPLAY_NAMEパターンを使うことができます。後者は、"{displayName} :: repetition {currentRepetition} of {totalRepetitions}"と等しいもので、各繰り返しの表示名はrepeatedTest() :: repetition 1 of 10repeatedTest() :: repetition 2 of 10などとなります。

現在の繰り返し回数と繰り返しの合計数の情報をプログラム的に集めるために、@RepeatedTestまたは@BeforeEach@AfterEachRepetitionInfoインスタンスを挿入することができます。

3.13.1. 繰り返しテストの例

この章の最後にあるRepeatedTestsDemoクラスは、繰り返しテストのいくつかの例を示しています。

repeatedTest()メソッドは、前章からの例です。一方、repeatedTestWithRepetitionInfo()は、現在繰り返されているテストの繰り返し合計数を得るためにRepetitionInfoインスタンスをテストに注入する方法を示しています。

その次の2つのメソッドは、@RepeatedTestのカスタム@DisplayNameを各繰り返しの表示名内に含ませる方法を示しています。customDisplayName()はカスタム表示名とカスタムパターンを組み合わせており、TestInfoを使って生成された表示名のフォーマットを検証しています。Repeat!@DisplayName宣言から来る{displayName}で、1/1{currentRepetition}/{totalRepetitions}から来ています。対照的に、customDisplayNameWithLongPattern()は、先ほど説明した事前定義のRepeatedTest.LONG_DISPLAY_NAMEパターンを使っています。

repeatedTestInGerman()は、繰り返しテストの表示名を他国言語(この場合はドイツ語です)に翻訳する機能を示しています。その結果、各繰り返しにおける名前は、Wiederholung 1 von 5Wiederholung 2 von 5などのようになります。

beforeEach()メソッドは@BeforeEachが付与されているため、各繰り返しテストの各繰り返し前に実行されます。TestInfoRepetitionInfoをこのメソッドに注入することで、現在実行されている繰り返しテストに関する情報を得ることができます。INFOログレベルでRepeatedTestsDemoを実行すると出力は次のようになります。

INFO: About to execute repetition 1 of 10 for repeatedTest
INFO: About to execute repetition 2 of 10 for repeatedTest
INFO: About to execute repetition 3 of 10 for repeatedTest
INFO: About to execute repetition 4 of 10 for repeatedTest
INFO: About to execute repetition 5 of 10 for repeatedTest
INFO: About to execute repetition 6 of 10 for repeatedTest
INFO: About to execute repetition 7 of 10 for repeatedTest
INFO: About to execute repetition 8 of 10 for repeatedTest
INFO: About to execute repetition 9 of 10 for repeatedTest
INFO: About to execute repetition 10 of 10 for repeatedTest
INFO: About to execute repetition 1 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 2 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 3 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 4 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 5 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 1 of 1 for customDisplayName
INFO: About to execute repetition 1 of 1 for customDisplayNameWithLongPattern
INFO: About to execute repetition 1 of 5 for repeatedTestInGerman
INFO: About to execute repetition 2 of 5 for repeatedTestInGerman
INFO: About to execute repetition 3 of 5 for repeatedTestInGerman
INFO: About to execute repetition 4 of 5 for repeatedTestInGerman
INFO: About to execute repetition 5 of 5 for repeatedTestInGerman
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() {
        // ...
    }

}

ConsoleLauncherまたはunicodeテーマを有効化したjunitPlatformTestGradleプラグインを使うと、RepeatedTestsDemoの実行結果は次のようなコンソール出力を行います。

├─ RepeatedTestsDemo ✔
│  ├─ repeatedTest() ✔
│  │  ├─ repetition 1 of 10 ✔
│  │  ├─ repetition 2 of 10 ✔
│  │  ├─ repetition 3 of 10 ✔
│  │  ├─ repetition 4 of 10 ✔
│  │  ├─ repetition 5 of 10 ✔
│  │  ├─ repetition 6 of 10 ✔
│  │  ├─ repetition 7 of 10 ✔
│  │  ├─ repetition 8 of 10 ✔
│  │  ├─ repetition 9 of 10 ✔
│  │  └─ repetition 10 of 10 ✔
│  ├─ repeatedTestWithRepetitionInfo(RepetitionInfo) ✔
│  │  ├─ repetition 1 of 5 ✔
│  │  ├─ repetition 2 of 5 ✔
│  │  ├─ repetition 3 of 5 ✔
│  │  ├─ repetition 4 of 5 ✔
│  │  └─ repetition 5 of 5 ✔
│  ├─ Repeat! ✔
│  │  └─ Repeat! 1/1 ✔
│  ├─ Details... ✔
│  │  └─ Details... :: repetition 1 of 1 ✔
│  └─ repeatedTestInGerman() ✔
│     ├─ Wiederholung 1 von 5 ✔
│     ├─ Wiederholung 2 von 5 ✔
│     ├─ Wiederholung 3 von 5 ✔
│     ├─ Wiederholung 4 von 5 ✔
│     └─ Wiederholung 5 von 5 ✔

3.14. パラメータ化テスト

パラメータ化テストを使うと、テストを異なる引数で複数回実行できるようになります。パラメータ化テストは、通常の@Testメソッドの代わりに@ParameterizedTestアノテーションを付与するだけで宣言することができます。さらに、各呼び出して供給されテストで消費される引数として、少なくとも1つのsourceを宣言する必要があります。

次の例は、パラメータ化テストを示していて、@ValueSourceアノテーションを使って引数のソースとしてString配列を指定しています。

@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
    assertTrue(isPalindrome(candidate));
}

上記のパラメータ化テストメソッドを実行すると、各呼び出しは別々にレポートされます。例えば、ConsoleLauncherは次のようなものを出力します。

palindromes(String) ✔
├─ [1] racecar ✔
├─ [2] radar ✔
└─ [3] able was I ere I saw elba ✔

⚠️ パラメータ化テストは、現在実験的な機能です。詳細については、実験的なAPIsをご覧ください。

3.14.1. 必要なセットアップ

パラメータ化テストを使うためには、junit-jupiter-paramsアーティファクトを依存関係に加える必要があります。詳細については、依存関係のメタデータをご覧ください。

3.15. 引数の消費

パラメータ化テストメソッドは典型的に、設定されたソース(引数のソースをご覧ください。)から直接、引数を消費します。引数ソースとメソッドパラメータのインデックスは1対1の相関関係に従います(@CsvSourceの例をご覧ください)。しかしながら、パラメータ化テストメソッドは、ソースから得た引数をひとつのオブジェクトに集約して、メソッドに渡すこともできます(引数集約をご覧ください)。追加的な引数もまた(例えば、TestInfoTestReporterなどのインスタンスを獲得するために)ParameterResolverによって提供されます。特に、パラメータ化テストメソッドは、次のルールに従って形式的なパラメータを宣言する必要があります。

この文脈で、インデックスされた引数とは、ArgumentsProviderによって提供されるArguments内で与えられたインデックスに対応する引数です。ArgumentsProviderは、パラメータ化メソッドが保持する形式的なパラメータリストにおいて同じインデックスにあるメソッドに引数として渡されます。アグリゲータは、ArgumentsAccessor型または@AggregateWithの付与されたパラメータです。

3.15.1. 引数のソース

すぐに使えるように、JUnit Jupiterは非常に多くのソースアノテーションを提供しています。次の各章はそれぞれ、簡潔な概要とそれぞれの例を提供しています。さらなる情報に関しては、org.junit.jupiter.params.providerパッケージのJavaDocを参照してください。

@ValueSource

@ValueSourceは最も単純なソースの1つです。リテラル値の配列を1つ設定することができ、パラメータ化テスト呼び出しにつき、1つのパラメータを提供できます。

次のリテラル値の型が@ValueSourceにサポートされています。

例えば、次の@ParameterizedTestメソッドはそれぞれ123の値とともに3回呼び出されます。

@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
    assertTrue(argument > 0 && argument < 4);
}

@EnumSource

@EnumSourceは、Enum定数に対して便利な機能を提供します。このアノテーションは、使われる定数を特定するために、オプションでnamesパラメータを提供します。省略する場合は、次の例のように全ての定数が使われます。

@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithEnumSource(TimeUnit timeUnit) {
    assertNotNull(timeUnit);
}

@ParameterizedTest
@EnumSource(value = TimeUnit.class, names = { "DAYS", "HOURS" })
void testWithEnumSourceInclude(TimeUnit timeUnit) {
    assertTrue(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));
}

@EnumSourceアノテーションはまた、テストメソッドに渡すパラメータを細かく制御するために、オプションでmodeパラメータを提供します。例えば、次の例では、enum定数プールからnamesを取り除いたり、正規表現を設定しています。

@ParameterizedTest
@EnumSource(value = TimeUnit.class, mode = EXCLUDE, names = { "DAYS", "HOURS" })
void testWithEnumSourceExclude(TimeUnit timeUnit) {
    assertFalse(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));
    assertTrue(timeUnit.name().length() > 5);
}
@ParameterizedTest
@EnumSource(value = TimeUnit.class, mode = MATCH_ALL, names = "^(M|N).+SECONDS$")
void testWithEnumSourceRegex(TimeUnit timeUnit) {
    String name = timeUnit.name();
    assertTrue(name.startsWith("M") || name.startsWith("N"));
    assertTrue(name.endsWith("SECONDS"));
}

@MethodSource

@MethodSourceでは、テストクラスまたは外部クラスのファクトリーメソッドを1つ以上使うことができます。ファクトリーメソッドは、StreamまたはIterableIterator、引数の配列を返す必要があります。さらに、ファクトリーメソッドには引数を与えられません。テストクラス内のファクトリーメソッドは、テストクラスに@TestInstance(Lifecycle.PER_CLASS)が付与されていない限り、staticである必要があります。一方、外部クラスのファクトリーメソッドは常にstaticである必要があります。

パラメータが1つだけ必要な場合は、次の例が示しているように、パラメータの型のインスタンスのStreamを返すことができます。

@ParameterizedTest
@MethodSource("stringProvider")
void testWithSimpleMethodSource(String argument) {
    assertNotNull(argument);
}

static Stream<String> stringProvider() {
    return Stream.of("foo", "bar");
}

@MethodSourceを通して明示的にファクトリーメソッドの名前を提供しない場合、JUnit Jupiterは、慣例にならって現在の@ParameterizedTestと同じ名前を持つファクトリーメソッドを探します。これを次の例で示します。

@ParameterizedTest
@MethodSource
void testWithSimpleMethodSourceHavingNoValue(String argument) {
    assertNotNull(argument);
}

static Stream<String> testWithSimpleMethodSourceHavingNoValue() {
    return Stream.of("foo", "bar");
}

DoubleStreamIntStreamLongStreamといったプリミティブ型のStreamもまた、次の例のようにサポートされています。

@ParameterizedTest
@MethodSource("range")
void testWithRangeMethodSource(int argument) {
    assertNotEquals(9, argument);
}

static IntStream range() {
    return IntStream.range(0, 20).skip(10);
}

テストメソッドが複数のパラメータを宣言している場合、下に示すようにArgumentsインスタンスのコレクションまたはストリームを返す必要があります。Arguments.of(Object...)は、Argumentsインターフェイスで定義されている静的なファクトリーメソッドです。

@ParameterizedTest
@MethodSource("stringIntAndListProvider")
void testWithMultiArgMethodSource(String str, int num, List<String> list) {
    assertEquals(3, str.length());
    assertTrue(num >=1 && num <=2);
    assertEquals(2, list.size());
}

static Stream<Arguments> stringIntAndListProvider() {
    return Stream.of(
        Arguments.of("foo", 1, Arrays.asList("a", "b")),
        Arguments.of("bar", 2, Arrays.asList("x", "y"))
    );
}

外部のstaticファクトリーメソッドは、次の例で示すように完全修飾メソッド名によって参照されます。

package example;

import java.util.stream.Stream;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

class ExternalMethodSourceDemo {

    @ParameterizedTest
    @MethodSource("example.StringsProviders#blankStrings")
    void testWithExternalMethodSource(String blankString) {
        // test with blank string
    }
}

class StringsProviders {

    static Stream<String> blankStrings() {
        return Stream.of("", " ", " \n ");
    }
}

@CsvSource

@CsvSourceは、引数リストをコンマ区切りの値(つまり、Stringリテラル)として表現できるようにします。

@ParameterizedTest
@CsvSource({ "foo, 1", "bar, 2", "'baz, qux', 3" })
void testWithCsvSource(String first, int second) {
    assertNotNull(first);
    assertNotEquals(0, second);
}

@CsvSourceは、シングルクォーテーション'を引用文字として使います。上の例と下の表の'baz, qux'の値をご覧ください。引用された空の値''は、空のStringとなります。一方、完全にの値はnull参照として解釈されます。null参照が対象とする型がプリミティブ型の場合、ArgumentConversionExceptionが投げられます。

入力例 引数リストの結果
@CsvSource({ "foo, bar" }) "foo", "bar"
@CsvSource({ "foo, 'baz, qux'" }) "foo", "baz, qux"
@CsvSource({ "foo, ''" }) "foo", ""
@CsvSource({ "foo, " }) "foo", null

@CsvFileSource

@CsvFileSourceは、CSVファイルをクラスパスから使えるようにします。パラメータ化テストが1回呼び出される度に、CSVファイルの各行が読み込まれます。

@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSource(String first, int second) {
    assertNotNull(first);
    assertNotEquals(0, second);
}
Country, reference
Sweden, 1
Poland, 2
"United States of America", 3

ℹ️ @CsvSourceで使われている構文とは対照的に、@CsvFileSourceでは引用文字としてダブルクォーテーション"を使います。上記の例の"United States of America"をご覧ください。引用された空の値””は、空のStringとなります。一方、完全にの値はnull参照として解釈されます。null参照が対象とする型がプリミティブ型の場合、ArgumentConversionExceptionが投げられます。

@ArgumentSource

@ArgumentSourceはカスタムの再利用可能なArgumentsProviderを特定するために使うことができます。

@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void testWithArgumentsSource(String argument) {
    assertNotNull(argument);
}

public class MyArgumentsProvider implements ArgumentsProvider {

    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
        return Stream.of("foo", "bar").map(Arguments::of);
    }
}

3.15.2. 引数変換

拡大的な変換

JUnit Jupiterは@ParamterizedTestに供給する引数のために、拡大的なプリミティブ変換をサポートしています。例えば、@ValueSource(ints = { 1, 2, 3 })が付与されたパラメータ化テストは、int型のみならず、longfloatdouble型の引数も受けることができます。

暗示的な変換

@CsvSourceのようなユースケースをサポートするために、JUnit Jupiterは組み込みの暗示的な型変換をいくつか提供しています。変換プロセスは、各メソッドパラメータの宣言された型に依存します。

例えば、@ParameterizedTestTimeUnit型のパラメータを宣言していて、ソースから供給された実際の型がStringであった場合、Stringは自動的に対応するTimeUnitenum定数に変換されます。

@ParameterizedTest
@ValueSource(strings = "SECONDS")
void testWithImplicitArgumentConversion(TimeUnit argument) {
    assertNotNull(argument.name());
}

Stringインスタンスは現在、次の対象型に暗示的に変換されます。

対象型
boolean/Boolean "true"true
byte/Byte "1" → (byte) 1
char/Character "o"'o'
short/Short "1" → (short) 1
int/Integer "1"1
long/Long "1"1L
float/Float "1"1.0f
double/Double "1"1.0d
Enumサブクラス "SECONDS"TimeUnit.SECONDS
java.io.File "/path/to/file"new File("path/to/file")
java.math.BigDecimal "123.456e789"new BigDecimal("123.456e789")
java.math.BigInteger "1234567890123456789"new BigInteger("1234567890123456789")
java.net.URI "http://junit.org/"URI.create("http://junit.org/")
java.net.URL "http://junit.org/"new URL("http://junit.org/")
java.nio.file.Path "/path/to/file"Paths.get("/path/to/file")
java.time.Instant "1970-01-01T00:00:00Z"Instant.ofEpochMilli(0)
java.time.LocalDateTime "2017-03-14T12:34:56.789"LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)
java.time.LocalDate "2017-03-14"LocalDate.of(2017, 3, 14)
java.time.LocalTime "12:34:56.789"LocalTime.of(12, 34, 56, 789_000_000)
java.time.OffsetDateTime "2017-03-14T12:34:56.789Z"OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)
java.time.OffsetTime "12:34:56.789Z"OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)
java.time.YearMonth "2017-03"YearMonth.of(2017, 3)
java.time.Year "2017"Year.of(2017)
java.time.ZonedDateTime "2017-03-14T12:34:56.789Z"ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)
java.util.Currency "JPY"Currency.getInstance("JPY")
java.util.Locale "en"new Locale("en")
java.util.UUID "d043e930-7b3b-48e3-bdbe-5a3ccfb833db"UUID.fromString("d043e930-7b3b-48e3-bdbe-5a3ccfb833db")

StringからObjectへの予備的な変換

Stringから上に列挙されている対象型への暗示的な変換に加えて、JUnit Jupiterでは、対象型が下の定義に合致したファクトリーメソッドまたはファクトリーコンストラクタを宣言する場合、Stringをその対象型へ自動変換する予備的な機構を提供します。

ℹ️ 複数のファクトリーメソッドが見つかった場合、それらは無視されます。ファクトリーメソッドファクトリーコンストラクタが見つかった場合、ファクトリーメソッドがコンストラクタの代わりに使われます。

例えば、次の@ParameterizedTestメソッドの中で、Book引数はBook.fromTitle(String)ファクトリーメソッドが呼び出されることで生成され、"42 Cats"が本のタイトルとして渡されます。

@ParameterizedTest
@ValueSource(strings = "42 Cats")
void testWithImplicitFallbackArgumentConversion(Book book) {
    assertEquals("42 Cats", book.getTitle());
}

public class Book {

    private final String title;

    private Book(String title) {
        this.title = title;
    }

    public static Book fromTitle(String title) {
        return new Book(title);
    }

    public String getTitle() {
        return this.title;
    }
}

明示的な変換

暗黙的な引数変換の代わりに、次の例のように@ConvertWithアノテーションを使うことで、あるパラメータに対して明示的にArgumentConverterを特定することができます。

@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithExplicitArgumentConversion(
        @ConvertWith(ToStringArgumentConverter.class) String argument) {

    assertNotNull(TimeUnit.valueOf(argument));
}

public class ToStringArgumentConverter extends SimpleArgumentConverter {

    @Override
    protected Object convert(Object source, Class<?> targetType) {
        assertEquals(String.class, targetType, "Can only convert to String");
        return String.valueOf(source);
    }
}

明示的な引数変換は、テストと拡張の開発者によって実装される必要があります。そのため、junit-jupiter-paramsでは、参照実装として使える明示的な引数変換器:JavaTimeArgumentConverterを提供しています。合成アノテーションであるJavaTimeConversionPatternを通して使うことができます。

@ParameterizedTest
@ValueSource(strings = { "01.01.2017", "31.12.2017" })
void testWithExplicitJavaTimeConverter(
        @JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) {

    assertEquals(2017, argument.getYear());
}

3.15.3. 引数集約

デフォルトでは、@ParameterizedTestメソッドに渡される各引数は、1つのメソッドパラメータに対応しています。その結果として、大量の引数を供給することが期待される引数ソースは、巨大なメソッドシグネチャになる可能性があります。

そのような場合、ArgumentAccessorを複数のパラメータの代わりに使うことができます。このAPIを使うことで、テストメソッドに渡された1つのパラメータを通して提供された引数にアクセスすることができます。さらに、暗黙的な変換で議論している型変換もサポートしています。

@ParameterizedTest
@CsvSource({
    "Jane, Doe, F, 1990-05-20",
    "John, Doe, M, 1990-10-22"
})
void testWithArgumentsAccessor(ArgumentsAccessor arguments) {
    Person person = new Person(arguments.getString(0),
                               arguments.getString(1),
                               arguments.get(2, Gender.class),
                               arguments.get(3, LocalDate.class));

    if (person.getFirstName().equals("Jane")) {
        assertEquals(Gender.F, person.getGender());
    }
    else {
        assertEquals(Gender.M, person.getGender());
    }
    assertEquals("Doe", person.getLastName());
    assertEquals(1990, person.getDateOfBirth().getYear());
}

ArgumentsAccessorのインスタンスは、ArgumentsAccessor型のいかなるパラメータに自動的に挿入されます

カスタムアグリゲータ

ArgumentsAccessorを用いた@ParameterizedTestメソッドの引数への直接アクセスとは別に、JUnit Jupiterはカスタムで再利用可能なアグリゲータの使用もサポートしています。

カスタムアグリゲータを使うためには、単にArgumentAggregatorインターフェイスを実装し、@ParameterizedTestメソッド内で互換可能なパラメータに対して@AggregateWithアノテーションを付与して登録するだけです。集約の結果は、パラメータ化テストが呼び出された時に、対応するパラメータへの引数として提供されます。

@ParameterizedTest
@CsvSource({
    "Jane, Doe, F, 1990-05-20",
    "John, Doe, M, 1990-10-22"
})
void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) {
    // perform assertions against person
}

public class PersonAggregator implements ArgumentsAggregator {
    @Override
    public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) {
        return new Person(arguments.getString(0),
                          arguments.getString(1),
                          arguments.get(2, Gender.class),
                          arguments.get(3, LocalDate.class));
    }
}

コードベースにまたがって複数のパラメータ化テストに対して繰り返し@AggregateWith(MyTypeAggregator.class)を宣言している場合、@AggregateWith(MyTypeAggregator.class)のメタアノテーションとして@CsvToMyTypeのようなカスタム合成アノテーションを作成できます。次の例は、カスタム@CsvToPersonアノテーションを用いた動作例を示しています。

@ParameterizedTest
@CsvSource({
    "Jane, Doe, F, 1990-05-20",
    "John, Doe, M, 1990-10-22"
})
void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) {
    // perform assertions against person
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AggregateWith(PersonAggregator.class)
public @interface CsvToPerson {
}

3.15.4. 表示名のカスタマイズ

デフォルトでは、パラメータ化テスト呼び出しの表示名は、呼び出しインデックスと特定の呼び出しに対する全ての引数のString表現を含んでいます。しかしながら、次の例のように@ParameterizedTestアノテーションのname属性によって呼び出し表示名をカスタマイズできます。

@DisplayName("Display name of container")
@ParameterizedTest(name = "{index} ==> first=''{0}'', second={1}")
@CsvSource({ "foo, 1", "bar, 2", "'baz, qux', 3" })
void testWithCustomDisplayNames(String first, int second) {
}

上記のメソッドをConsoleLauncherを使って実行すると、次のような出力が表示されます。

Display name of container ✔
├─ 1 ==> first='foo', second=1 ✔
├─ 2 ==> first='bar', second=2 ✔
└─ 3 ==> first='baz, qux', second=3 ✔

カスタム表示名では、次のプレースホルダがサポートされています。

プレースホルダ 説明
{index} 現在の呼び出しインデックス(1始まり)
{arguments} 完全な引数リスト(CSV形式)
{0}, {1}, ... 各引数

3.15.5. ライフサイクルと相互運用性

パラメータ化テストの各呼び出しは、通常の@Testメソッドと同じライフサイクルを持っています。例えば、各呼び出し前には@BeforeEachメソッドが実行されます。動的テストと同じように、呼び出しはIDEのテストツリーでは一つ一つ表れます。同一のテストクラスに、自由に@Test@ParameterizedTestを混ぜることができます。

@ParameterizedTestメソッドと合わせてParameterResolver拡張を使うことができます。しかしながら、引数ソースによって解決されたパラメータは、引数リストの最初に来る必要があります。テストクラスは様々なパラメータリストを持つパラメータ化テストと同様に通常のテストを含むこともあるので、引数ソースからの値は@BeforeEachといったライフサイクルメソッドやテストクラスコンストラクタは解決されません。

@BeforeEach
void beforeEach(TestInfo testInfo) {
    // ...
}

@ParameterizedTest
@ValueSource(strings = "foo")
void testWithRegularParameterResolver(String argument, TestReporter testReporter) {
    testReporter.publishEntry("argument", argument);
}

@AfterEach
void afterEach(TestInfo testInfo) {
    // ...
}

3.16. テストテンプレート

@TestTemplateメソッドは、通常のテストケースではなく、むしろテストケースのためのテンプレートです。したがって、@TestTemplateは、登録されたプロバイダによって返される呼び出し文脈の数に応じて複数回呼び出されるものとして設計されています。そのため、登録されたTestTemplateInvocationContextProvider拡張と併せて使われる必要があります。テストテンプレートメソッドの各呼び出しは、通常の@Testメソッドの実行と同じように振る舞い、同じライフサイクルのコールバックと拡張が完全にサポートされています。用法例については、テストテンプレートに対する呼び出し文脈の提供を参照ください。

3.17. 動的テスト

アノテーションで説明したJUnit Jupiterの標準的な@Testアノテーションは、JUnit 4の@Testアノテーションに非常に似通っています。どちらもテストケースを実装したメソッドです。これらのテストケースはコンパイル時に完全に決定するという意味では静的であり、それらの振る舞いは実行時に変更することはできません。アサンプションは、意図的にかなり表現性に制限のあるものですが、動的な振る舞いの基本的な形式を提供します

これらの標準的なテストに加えて、全く新しい種類のテストプログラミングモデルがJUnit Jupiterでは導入されました。この新しいテストとは、動的テストです。動的テストは、@TestFactoryが付与されたファクトリーメソッドによって、実行時に生成されます。

@Testメソッドとは対照的に、@TestFactoryメソッド自身はテストケースではなく、むしろテストケースのためのファクトリーです。そのため、動的テストはファクトリーの産出物となります。技術的なことを言うと、@TestFactoryメソッドは、DynamicNodeインスタンスのStreamまたはCollectionIterableIteratorを返さなければなりません。DynamicNodeのインスタンス化可能なサブクラスはDynamicContainerDynamicTestです。DynamicContainerインスタンスは、表示名と動的な子ノードのリストで構成されており、動的なノードの任意なネスト階層を生成できます。DynamicTestインスタンスは、遅延実行され、テストケースの動的で非決定的な生成が可能となります。

@TestFactoryによって返されるStreamはいずれも、stream.close()を呼ぶことで適切に閉じられます。これによって、Files.lines()のような資源を安全に使うことができます。

@Testメソッドと同様に、@TestFactoryメソッドはprivateまたはstaticは不可で、ParameterResolversで解決されるパラメータをオプションで宣言できます。

DynamicTestは実行時に生成されるテストケースで、表示名Executableで構成されています。Executable@FunctionalInterfaceで、このインターフェイスはラムダ表現またはメソッド参照として提供されることができる動的テストの実装であることを意味します。

⚠️ 動的テストのライフサイクル:動的テストの実行ライフサイクルは、標準的な@Testケースとは全く異なります。特に、各動的テストに対してのライフサイクルのコールバックはありません。このことは、@BeforeEach@AfterEach、それに対応した拡張コールバックは@TestFactoryメソッドに対して実行され、各動的テストには実行されないことを意味します。つまり、動的テストに対してラムダ表現でテストインスタンスからフィールドにアクセスしても、それらのフィールドは、同じ@TestFactoryメソッドで生成された個々の動的テストの実行中は、コールバックメソッドやその拡張によってリセットされません。

JUnit Jupiter 5.2.0時点では、動的テストは常にファクトリーメソッドによって生成される必要があります。しかしながら、これは後のリリースの登録機能によって補完されるかもしれません。

⚠️ 動的テストは現在実験的な機能です。詳細に関しては、実験的なAPIsをご覧ください。

3.17.1. 動的テストの例

次のDynamicTestsDemoクラスは、テストファクトリーと動的テストのいくつかの例を示しています。

最初のメソッドは不正な型を返しています。不正な返り値の型はコンパイル時に検出することができないため、実行時に検出されJUnitExceptionが投げられます。

次の5つもメソッドは、DynamicTestインスタンスのCollectionまたはIterableIteratorStreamを生成する非常に単純な例です。これらの例のほとんどは実際には動的振る舞いを示しておらず、単に原則的にサポートされている返却型を示しています。しかしながら、dynamicTestsFromStream()dynamicTestsFromIntStream()は、Stringのセットや入力値の範囲に対する動的テストの生成がいかに簡単かを示しています。

次のメソッドは、性質上、真に動的なものです。generateRandomNumberOfTests()、ランダム数を生成するIteratorと表示名生成器、テスト実行器を実装しており、その3つをDynamicTest.stream()に提供しています。generateRandomNumberOfTests()の非決定的な振る舞いは、もちろんテスト反復可能性に抵触しており、注意深く取り扱われるべきではありますが、動的テストの表現性と能力を示しています。

最後のメソッドは、DynamicContainerを使って動的テストのネスト階層を生成しています。

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.ThrowingConsumer;

class DynamicTestsDemo {

    // This will result in a JUnitException!
    @TestFactory
    List<String> dynamicTestsWithInvalidReturnType() {
        return Arrays.asList("Hello");
    }

    @TestFactory
    Collection<DynamicTest> dynamicTestsFromCollection() {
        return Arrays.asList(
            dynamicTest("1st dynamic test", () -> assertTrue(true)),
            dynamicTest("2nd dynamic test", () -> assertEquals(4, 2 * 2))
        );
    }

    @TestFactory
    Iterable<DynamicTest> dynamicTestsFromIterable() {
        return Arrays.asList(
            dynamicTest("3rd dynamic test", () -> assertTrue(true)),
            dynamicTest("4th dynamic test", () -> assertEquals(4, 2 * 2))
        );
    }

    @TestFactory
    Iterator<DynamicTest> dynamicTestsFromIterator() {
        return Arrays.asList(
            dynamicTest("5th dynamic test", () -> assertTrue(true)),
            dynamicTest("6th dynamic test", () -> assertEquals(4, 2 * 2))
        ).iterator();
    }

    @TestFactory
    Stream<DynamicTest> dynamicTestsFromStream() {
        return Stream.of("A", "B", "C")
            .map(str -> dynamicTest("test" + str, () -> { /* ... */ }));
    }

    @TestFactory
    Stream<DynamicTest> dynamicTestsFromIntStream() {
        // Generates tests for the first 10 even integers.
        return IntStream.iterate(0, n -> n + 2).limit(10)
            .mapToObj(n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0)));
    }

    @TestFactory
    Stream<DynamicTest> generateRandomNumberOfTests() {

        // Generates random positive integers between 0 and 100 until
        // a number evenly divisible by 7 is encountered.
        Iterator<Integer> inputGenerator = new Iterator<Integer>() {

            Random random = new Random();
            int current;

            @Override
            public boolean hasNext() {
                current = random.nextInt(100);
                return current % 7 != 0;
            }

            @Override
            public Integer next() {
                return current;
            }
        };

        // Generates display names like: input:5, input:37, input:85, etc.
        Function<Integer, String> displayNameGenerator = (input) -> "input:" + input;

        // Executes tests based on the current input value.
        ThrowingConsumer<Integer> testExecutor = (input) -> assertTrue(input % 7 != 0);

        // Returns a stream of dynamic tests.
        return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor);
    }

    @TestFactory
    Stream<DynamicNode> dynamicTestsWithContainers() {
        return Stream.of("A", "B", "C")
            .map(input -> dynamicContainer("Container " + input, Stream.of(
                dynamicTest("not null", () -> assertNotNull(input)),
                dynamicContainer("properties", Stream.of(
                    dynamicTest("length > 0", () -> assertTrue(input.length() > 0)),
                    dynamicTest("not empty", () -> assertFalse(input.isEmpty()))
                ))
            )));
    }

}

4. テストの実行

4.1. IDEサポート

4.1.1. IntelliJ IDEA

IntelliJ IDEAは、バージョン 2016.2から、JUnit Platform上でのテスト実行をサポートしています。詳細については、IntelliJ IDEAブログの記事をご覧ください。しかしながら、2017.3以降のバージョンのIDEAを使うことをお薦めします。というのも、これらの新しいバージョンのIDEAは、プロジェクト内で使われているAPIバージョンに応じて次のJARを自動でダウンロードしてくれるからです:junit-platform-launcherjunit-jupiter-enginejunit-vintage-engine

⚠️ IDEA 2017.3より前のIntelliJ IDEAは、特定のバージョンのJUnit 5をバンドルします。そのため、新しいバージョンのJUnit JUpiterを使いたい場合、IDE内でのテスト実行はバージョンの衝突によって失敗する恐れがあります。そのような場合、ItelliJ IDEAにバンドルされたものではない新しいJUnit 5を使うために下の説明に従ってください。

異なるJUnit 5のバージョン(例えば、5.2.0)を使うには、対応するバージョンのjunit-platform-launcherjunit-jupiter-enginejunit-vintage-engineのJARをクラスパスに含める必要があるかもしれません。

追加のGradleの依存関係

// Only needed to run tests in a version of IntelliJ IDEA that bundles older versions
testRuntime("org.junit.platform:junit-platform-launcher:1.2.0")
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.2.0")
testRuntime("org.junit.vintage:junit-vintage-engine:5.2.0")

追加のMavenの依存関係

<!-- Only needed to run tests in a version of IntelliJ IDEA that bundles older versions -->
<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-launcher</artifactId>
    <version>1.2.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.2.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <version>5.2.0</version>
    <scope>test</scope>
</dependency>

4.1.2. Eclipse

Eclipse IDEは、Eclipse Oxygen.1a (4.7.1a)からJUnit Platformサポートを提供しています。

EclipseでJUnit 5を使うためのさらなる情報については、Eclipse Project Oxygen.1a (4.7.1a) - New and Noteworthy記事の公式なEclipse support for JUnit 5の章をご覧ください。

4.1.3. 他のIDE

この記事を書いている時点では、IDEAとEclipseを除き、IDE内でJUnit Platform上のテスト実行を直接サポートしているIDEはありません。しかしながら、いまいまでも自身のIDEでJUnit 5を試すことができるように、JUnitチームは2つの仲介的な解決法を提供しています。それはコンソール・ラウンチャーを手動で使うか、JUnit 4ベースのランナーでテストを実行することができます。

4.2. ビルドサポート

4.2.1. Gradle

バージョン4.6から、GradleはJUnit Platform上でのテスト実行をネイティブ・サポートしています。それを有効化するためには、build.gradle内のtestタスク宣言でuseJUnitPlatform()を指定するだけです。

test {
    useJUnitPlatform()
}

タグやエンジンによるフィルタリングもサポートされています:

test {
    useJUnitPlatform {
        includeTags 'fast', 'smoke & feature-a'
        // excludeTags 'slow', 'ci'
        includeEngines 'junit-jupiter'
        // excludeEngines 'junit-vintage'
    }
}

オプションの包括的なリストについては、Gradleの公式ドキュメントを参照してください。

⚠️ JUnit Platform Gradleプラグインは非推奨:JUnitチームによって開発された非常に初歩的なjunit-platform-gradle-pluginは、JUnit Platform 1.2で非推奨になり、1.3では廃止されます。Gradleの標準的なtestタスクに移行してください。

設定パラメータ

標準的なGralde testタスクは現在、テスト発見や実行に役立つJUnit Platform設定パラメータを設定するための専用DSLを提供していません。しかしながら、(下に示すように)システムプロパティやjunit-platform.propertiesを通して、ビルドスクリプト内で設定パラメータを提供できます。

test {
    // ...
    systemProperty 'junit.jupiter.conditions.deactivate', '*'
    systemProperties = [
        'junit.jupiter.extensions.autodetection.enabled': 'true',
        'junit.jupiter.testinstance.lifecycle.default': 'per_class'
    ]
    // ...
}

テストエンジンの設定

テストを実行するには、TestEngine実装をクラスパス上に配置する必要があります。

JUnit Jupiterベースのテストへのサポートを設定するには、次のように、testCompileの依存関係にJUnit Jupiter APIとtestRuntimeの依存関係にJUnit Jupiter TestEngine実装を設定する必要があります。

dependencies {
    testCompile("org.junit.jupiter:junit-jupiter-api:5.2.0")
    testRuntime("org.junit.jupiter:junit-jupiter-engine:5.2.0")
}

JUnit Platformは、次のように、testCompileの依存関係にJUnit 4とtestRuntimeの依存関係にJUnit Vintage TestEngine実装を設定することで、JUnit 4ベースのテストを実行することができます。

dependencies {
    testCompile("junit:junit:4.12")
    testRuntime("org.junit.vintage:junit-vintage-engine:5.2.0")
}

ログの設定(オプション)

JUnitは、JULで知られているjava.util.loggingパッケージ内のJava Logging APIsを使って、警告とデバッグ情報を排出しています。設定オプションについては、LogManagerの公式ドキュメントを参照してください。

その他に、ログメッセージをLog4JLogbackといった他のロギングフレームワークにログメッセージをリダイレクトすることもできます。LogManagerのカスタム実装を提供しているロギングフレームワークを使うには、java.util.logging.managerシステムプロパティに、使うLogManager実装の完全修飾クラス名を設定してください。下の例では、Log4j 2.xの設定方法を示しています(詳細については、Log4J JDK ロギングアダプタをご覧ください)。

test {
    systemProperty 'java.util.logging.manager', 'org.apache.logging.log4j.jul.LogManager'
}

他のロギングフレームワークは、java.util.loggingを使ってログされたメッセージをリダイレクトするための他の方法を提供しています。例えば、Logbackでは、実行時のクラスパスに依存関係を追加することで、JULからSLF4Jへの橋渡しを使うことができます。

4.2.2. Maven

JUnitチームは、mvn testを通してJUnit 4とJUnit Jupiterのテストを実行できるようなMaven Surefireのための基本的なプロバイダを開発しました。junit5-jupiter-starter-mavenプロジェクト内のpom.xmlファイルは、開始ポイントとしてのプロバイダの利用方法を示しています。

ℹ️ unit-platform-surefire-provider を用いて Maven Surefire 2.21.0 を使ってください。

...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.21.0</version>
            <dependencies>
                <dependency>
                    <groupId>org.junit.platform</groupId>
                    <artifactId>junit-platform-surefire-provider</artifactId>
                    <version>1.2.0</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
...

テストエンジンの設定

Maven Surefireでテストを実行するには、TestEngine実装を実行時クラスパスに加える必要があります。

JUnit Jupiterベースのテストへのサポートを設定するには、次のように、JUnit Jupiter API上にtestの依存関係を設定し、maven-surefire-pluginの依存関係にJUnit Jupiter TestEngine実装を追加してください。

...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.21.0</version>
            <dependencies>
                <dependency>
                    <groupId>org.junit.platform</groupId>
                    <artifactId>junit-platform-surefire-provider</artifactId>
                    <version>1.2.0</version>
                </dependency>
                <dependency>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-engine</artifactId>
                    <version>5.2.0</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
...
<dependencies>
    ...
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.2.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>
...

JUnit Platform Surefire Providerは、次のように、JUnit 4上にtestの依存関係を設定し、maven-surefire-pluginの依存関係にJUnit Vintage TestEngine実装を追加することで、JUnit 4ベースのテストを実行できます。

...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.21.0</version>
            <dependencies>
                <dependency>
                    <groupId>org.junit.platform</groupId>
                    <artifactId>junit-platform-surefire-provider</artifactId>
                    <version>1.2.0</version>
                </dependency>
                ...
                <dependency>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                    <version>5.2.0</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
...
<dependencies>
    ...
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
...

単一のテストクラスを実行

JUnit Platrform Surefire Providerは、Maven Surefire PluginによってサポートされているtestJVMシステムプロパティをサポートしています。例えば、org.example.MyTest内のテストメソッドだけを実行するには、コマンドラインから mvn -Dtest=org.example.MyTest test を実行してください。さらなる詳細については、Mavne Surefire Pluginのドキュメントを参照してください。

テストクラス名によるフィルタリング

Maven Surefire Pluginは次のパターンにマッチする完全修飾名を持つテストクラスをスキャンします。

さらに、全てのネストされたクラス(静的なメンバークラスも含む)はデフォルトでは除外されます。

しかしながら、このデフォルトの振る舞いは、includeexcludeの規則をpom.xml明示的に設定にすることで、上書きできます。例えば、Maven Surefireに静的なメンバークラスを除外させないためには、除外規則を上書きします。

Maven Surefireの除外規則の上書き

...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.21.0</version>
            <configuration>
                <excludes>
                    <exclude/>
                </excludes>
            </configuration>
            ...
        </plugin>
    </plugins>
</build>
...

詳細については、Maven Surefireのテストの包含と除外のドキュメントをご覧ください。

タグによるフィルタリング

次の設定プロパティを用いることで、タグまたはタグ表現によってテストをフィルターすることができます。 - タグまたはタグ表現を算入するには、groupsまたはincludeTagsを使ってください。 - タグまたはタグ表現を除外するには、excludedGroupsまたはexcludeTagsを使ってください。

...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.21.0</version>
            <configuration>
                <properties>
                    <includeTags>acceptance | !feature-a</includeTags>
                    <excludeTags>integration, regression</excludeTags>
                </properties>
            </configuration>
            <dependencies>
                ...
            </dependencies>
        </plugin>
    </plugins>
</build>
...

設定パラメータ

configurationParametersプロパティを宣言し、(下に示すように)Java Properties ファイル構文を使うかjunit-platform.propertiesファイルを通してキーバリューペアを提供することで、テスト探索と実行に影響を与えるJUnit Platform設定パラメータをセットすることができます。

...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.21.0</version>
            <configuration>
                <properties>
                    <configurationParameters>
                        junit.jupiter.conditions.deactivate = *
                        junit.jupiter.extensions.autodetection.enabled = true
                        junit.jupiter.testinstance.lifecycle.default = per_class
                    </configurationParameters>
                </properties>
            </configuration>
            <dependencies>
                ...
            </dependencies>
        </plugin>
    </plugins>
</build>
...

4.2.3. Ant

Antのバージョン1.10.3から、新しいjunitlauncherタスクが導入され、JUnit Platform上でのテスト実行がネイティブサポートされました。junitlauncherタスクは単独で、JUnit Platformを起動して選択されたテストコレクションを渡します。JUnit Platformはその後、テストの探索と実行を登録されたテストエンジンに委譲します。

junitlauncherタスクは、リソースコレクションといったネイティブなAnt構造になるべく密接に連携しています。これにより、ユーザがテストエンジンに実行してほしいテストを選択する際、他多数のコアなAntタスクと比べて、一貫して自然な感覚を与えています。

ℹ️ Ant 1.10.3にあるjunitlauncherタスクのバージョンは、JUnit Platformを起動するための基本的で最小のサポートを提供しています。追加的な機能強化(分離したJVMへのテスト分岐のサポートも含む)が次のAntのリリースで利用可能になる予定です。

junit5-jupiter-starter-antプロジェクト内のbuild.xmlは、開始ポイントとしてタスクの利用方法を示しています。

基本的な使い方

次の例は、junitlauncherタスクに1つのテストクラス(つまり、org.myapp.test.MyFirstJUnit5Test)を選ぶための設定方法を示しています。

<path id="test.classpath">
    <!-- The location where you have your compiled classes -->
    <pathelement location="${build.classes.dir}" />
</path>

<!-- ... -->

<junitlauncher>
    <classpath refid="test.classpath" />
    <test name="org.myapp.test.MyFirstJUnit5Test" />
</junitlauncher>

test要素を使うことで、実行したいテストを1つ選択できます。classpath要素はを使うことで、JUnit Platformを起動するために使うクラスパスを指定できます。このクラスパスは、実行に加えるテストクラスを配置するためにも使われます。

次の例は、複数の位置からテストクラスを選ぶためのjunitlauncherタスクの設定方法を示しています。

<path id="test.classpath">
    <!-- The location where you have your compiled classes -->
    <pathelement location="${build.classes.dir}" />
</path>
....
<junitlauncher>
    <classpath refid="test.classpath" />
    <testclasses outputdir="${output.dir}">
        <fileset dir="${build.classes.dir}">
            <include name="org/example/**/demo/**/" />
        </fileset>
        <fileset dir="${some.other.dir}">
            <include name="org/myapp/**/" />
        </fileset>
    </testclasses>
</junitlauncher>

上の例では、testclasses要素によって、異なる位置にある複数のテストクラスを選択しています。

用法と設定オプションのさらなる詳細については、junitlauncherタスクに関するAntの公式ドキュメントをご覧ください。

4.3. コンソールラウンチャー

ConsoleLauncherはコマンドラインJavaアプリケーションで、JUnit Platformをコンソールから起動できます。例えば、JUnit VintageとJUnit Jupiterテストを実行し、テストの実行結果をコンソールに出力します。

全ての依存関係が含まれた実行可能なjunit-platform-console-standalone-1.2.0.jarは、Mavenセントラルレポジトリ内のjunit-platform-console-standaloneディレクトリ以下に公開されています。スタンドアローンなConsoleLauncherは次のように実行できます。

java -jar junit-platform-console-standalone-1.2.0.jar <[Options>

これが出力の例です。

├─ JUnit Vintage
│  └─ example.JUnit4Tests
│     └─ standardJUnit4Test ✔
└─ JUnit Jupiter
   ├─ StandardTests
   │  ├─ succeedingTest() ✔
   │  └─ skippedTest() ↷ for demonstration purposes
   └─ A special test case
      ├─ Custom test name containing spaces ✔
      ├─ ╯°□°)╯ ✔
      └─ 😱 ✔

Test run finished after 64 ms
[         5 containers found      ]
[         0 containers skipped    ]
[         5 containers started    ]
[         0 containers aborted    ]
[         5 containers successful ]
[         0 containers failed     ]
[         6 tests found           ]
[         1 tests skipped         ]
[         5 tests started         ]
[         0 tests aborted         ]
[         5 tests successful      ]
[         0 tests failed          ]

ℹ️ 終了コード ConsoleLauncherは、いずれかのコンテナまたはテスト失敗した場合はステータスコード1、そうでない場合はステータスコード0を返します。

4.3.1. オプション

オプション                                       説明
------                                         -----------
-h, --help                                     ヘルプ情報を表示します。
--disable-ansi-colors                          出力でANSIカラーを無効化します。(全ての
                                                 ターミナルではサポートされていません。)
--details <[none,summary,flat,tree,verbose]    テスト実行時の出力詳細モードを選択します。
  >                                              次のうち、どれか1つを使います: [none, summary, flat,
                                                 tree, verbose]. 'none'が選択された場合、
                                                 サマリーとテスト失敗のみが表示されます。 (default: tree)
--details-theme <[ascii,unicode]>              テスト実行時の出力詳細ツリーのテーマを選択します。
                                                 次のうち、どれか1つを使います: [ascii, unicode]
                                                 (default: unicode)
--class-path, --classpath, --cp <Path:         追加的なクラスパスエントリを提供します。 --
  path1:path2:...>                               例えば、テストエンジンとその依存関係を追加でき
                                                 ます。このオプションは繰り返し可能です。
--reports-dir <Path>                           特定のローカルディレクトリへのレポート出力を有効化
                                                 します。(ディレクトリが存在しない場合は生成されます。)
--scan-modules                                 実験的:テスト発見で用いる全ての解決済みモジュールを
                                                 スキャンします。
-o, --select-module <String: module name>      実験的:テスト発見で用いる1つのモジュールを
                                                 選択します。このオプションは繰り返し可能です。
--scan-class-path, --scan-classpath [Path:     クラスパスまたは明示的なクラスパスのルート上の
  path1:path2:...]                               全てのディレクトリをスキャンします。引数がない場合、
                                                 -cp(ディレクトリとJARファイル)を通して供給された
                                                 追加的なクラスパスエントリと同様にシステムクラスパス
                                                 のみがスキャンされます。
                                                 クラスパス上にない明示的なクラスパスのルートは、
                                                 静かに無視されます。このオプションは繰り返し可能です。
-u, --select-uri <URI>                         テスト発見のためのURIを選択します。
                                                 このオプションは繰り返し可能です。
-f, --select-file <String>                     テスト発見のためのファイルを選択します。
                                                 このオプションは繰り返し可能です。
-d, --select-directory <String>                テスト発見のためのディレクトリを選択します。
                                                 このオプションは繰り返し可能です。
-p, --select-package <String>                  テスト発見のためのパッケージを選択します。
                                                 このオプションは繰り返し可能です。
-c, --select-class <String>                    テスト発見のためのクラスを選択します。
                                                 このオプションは繰り返し可能です。
-m, --select-method <String>                   テスト発見のためのメソッドを選択します。
                                                 このオプションは繰り返し可能です。
-r, --select-resource <String>                 テスト発見のためのクラスパス・リソースを選択します。
                                                 このオプションは繰り返し可能です。
-n, --include-classname <String>               完全修飾名がマッチするクラスのみを内包するための
                                                 正規表現を提供します。不要なクラス読み込みを避ける
                                                 ために、デフォルトのパターンは、クラス名が"Test"で始まるか、
                                                 "Test"または"Tests"で終わるクラスのみを内包
                                                 します。このオプションが繰り返し使われる場合、全ての
                                                 パターンはORを使って結合されます。
                                                 (default: ^(Test.*|.+[.$]Test.*|.*Tests?)$)
-N, --exclude-classname <String>               完全修飾名がマッチするクラスのみを除外するための
                                                 正規表現を提供します。このオプションが繰り返し使われる
                                                 場合、全てのパターンはORを使って結合されます。
--include-package <String>                     テスト実行に含まれるパッケージを提供します。
                                                 このオプションは繰り返し可能です。
--exclude-package <String>                     テスト実行から除外するパッケージを提供します。
                                                 このオプションは繰り返し可能です。
-t, --include-tag <String>                     タグがマッチするテストのみを内包するための
                                                 タグまたはタグ表現を提供します。このオプションが繰り返し
                                                 使われる場合、全てのパターンはORを使って結合され
                                                 ます。
-T, --exclude-tag <String>                     タグがマッチするテストのみを除外するための
                                                 タグまたはタグ表現を提供します。このオプションが繰り返し
                                                 使われる場合、全てのパターンはORを使って結合され
                                                 ます。
-e, --include-engine <String>                  テスト実行で内包するエンジンのIDを提供します。
                                                 このオプションは繰り返し可能です。
-E, --exclude-engine <String>                  テスト実行から除外するエンジンのIDを提供します。
                                                 このオプションは繰り返し可能です。
--config <key=value>                           テスト発見・実行のための設定パラメータをセットします。
                                                 このオプションは繰り返し可能です。

4.4. JUnit 4を用いてJUnit Platformを実行

JUnitPlatformランナーは、JUnit 4ベースのRunnerであり、JUnit 4環境内のJUNit Platform上でサポートされているプログラミングモデルを持つテスト(例えば、JUnit Jupiterテストクラス)は全て実行できます。

@RunWith(JUnitPlatform.class)をクラスに付与することで、JUnit 4をサポートしているもののJUnit PlatformはまだサポートしていないIDEやビルドシステムであっても実行できます。

ℹ️ JUnit PlatformはJUnit 4にはない特徴を持っているので、そのランナーはJUnit Platformのサブセットのみをサポートしています。特に、レポート機能についてが当てはまります(表示名vs技術的な名称をご覧ください)。しかし、さしあたりJUnitPlatformランナーはとっかかりやすい方法です。

4.4.1. セットアップ

次のアーティファクトとそれらの依存関係がクラスパス上に必要です。グループIDやアーティファクトID、バージョンに関する詳細は依存関係のメタデータをご覧ください。

明示的な依存関係

推移的な依存関係

4.4.2. 表示名vs技術的な名称

@RunWith(JUnitPlatform.class)を通して実行するクラスにカスタム表示名を定義するには、単に@SuiteDisplayNameをクラスに付与してカスタム値を提供するだけです。

デフォルトでは、表示名はテストアーティファクトのために使われます。しかしながら、GradleやMavenといったビルドツールでテスト実行するためにJUnitPlatrformランナーが使われる場合、生成されたテストレポートはしばしば、テストクラス名や特殊文字を含むカスタム表示名のような短い表示名の代わりに、テストアーティファクトの技術的な名称(例えば、完全修飾クラス名)を含む必要があります。レポート目的のために技術的な名称を有効化するには、単に@RunWith(JUnitPlatform.class)と一緒に@UseTechnicalNamesアノテーションを宣言するだけです。

@UseTechnicalNamesの存在は、@SuiteDisplayNameを通して設定されたいかなるカスタム表示名を上書きすることに注意してください。

4.4.3. 単一のテストクラス

JUnitPlatformランナーを使う1つの方法は、テストクラスに直接@RunWith(JUnitPlatform.class)を付与することです。次の例にあるテストメソッドはorg.junit.Test(JUnit Vintage)ではなく、org.junit.jupiter.api.Test(JUnit JUpiter)が付与されていることに注意してください。さらにこの場合では、テストクラスはpublicでないといけません。そうでないと、IDEやビルドツールがJUnit 4のテストクラスとして認識しない恐れがあります。

import static org.junit.jupiter.api.Assertions.fail;

import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
public class JUnit4ClassDemo {

    @Test
    void succeedingTest() {
        /* no-op */
    }

    @Test
    void failingTest() {
        fail("Failing for failing's sake.");
    }

}

4.4.4. テストスイート

複数のテストクラスがある場合、次の例のようにテストスイートを作成できます。

import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.SuiteDisplayName;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
@SuiteDisplayName("JUnit 4 Suite Demo")
@SelectPackages("example")
public class JUnit4SuiteDemo {
}

JUnit4SuiteDemoは、exampleパッケージとそのサブパッケージ内にある全てのテストを発見・実行します。デフォルトでは、Testで始まるか、TestまたはTestsで終わる名前を持つテストクラスのみが含まれます。

ℹ️ 追加的な設定オプション @SelectPackagesだけでなく、テスト発見・フィルタリング用の多くの設定オプションがあります。詳細については、Javadocをご覧ください。

4.5. 設定パラメータ

どのテストクラスとテストエンジンを内包するか、どのパッケージをスキャンするか、などをプラットフォームに知らせるのに加えて、特定のテストエンジンや登録した拡張を指定するための追加的なカスタム設定パラメータの提供が必要な場合があります。例えば、JUnit Jupiter TestEngineは次のユースケースのための設定パラメータをサポートしています。

設定パラメータはテキストベースのキーバリューペアで、次の仕組みの内の1つを通してJUnit Platform上で実行しているテストエンジンに供給されます。

  1. configurationParameter()configurationParameters()メソッド。Launcher APIに供給するリクエストを構築するLauncherDiscoveryRequestBuilder内に実装されています。JUnit Platformによって提供されているツールの内の1つを通してテスト実行している場合、次のように、設定パラメータを指定できます。
  1. JVMシステムプロパティ。
  2. JUnit Platform設定ファイル:junit-platform.propertiesという名前でクラスパスのルートに置かれたJavaのPropertiesファイルの構文規則に従ったファイル。

ℹ️ 設定パラメータは上で定義された順序で探索されます。結果として、Launcherに直接供給された設定パラメータは、システムプロパティと設定ファイルを通して供給されたパラメータよりも優先して使われます。同じように、システムプロパティを通して供給された設定パラメータは、設定ファイルを通して供給された設定パラメータよりも優先して使われます。

4.6. タグ表現

タグ表現は、ブーリアン(boolean)表現で、!&|オペレータが使われます。さらに、()もオペレータの優先順位を調節するために使われます。

表1. オペレータ(優先順位の降順)

オペレータ 意味 結合性
! not
& and
| or

いくつかの観点でテストにタグ付けしている場合、タグ表現は実行するテストの選択を助けてくれます。テストタイプ(例えば、microintegrationend-to-end)や特徴(foobarbaz)によってタグ付けするときは、次のタグ表現が役に立つでしょう。

タグ表現 選択
foo fooの全てのテスト
bar | baz barの全てのテストに加えてbazの全てのテスト
bar & baz barbaz相互に関わる全てのテスト
foo & !end-to-end fooのうち、end-to-endでない全てのテスト
(micro | integration) & (foo | baz) fooまたはbazのうち、microまたはintegrationの全てのテスト

5. 拡張モデル

5.1. 概要

JUnit 4において競合しているRunner@Rule@ClassRuleの拡張ポイントとは対照的に、JUnit Jupiterの拡張モデルは単一の包括的なコンセプト:Extension APIからなっています。しかしながら、Extension自体はただのマーカーインターフェイスであることに注意してください。

5.2. 拡張の登録

拡張は、@ExtendWithを通して宣言的に、または@RegisterExtensionを通してプログラム的に、JavaのServiceLoader機構を通して自動的に登録されます。

5.2.1. 宣言的な拡張登録

テストインターフェイスまたはテストクラス、テストメソッド、カスタム合成アノテーション@ExtendWith(…​)を付与し、登録する拡張のクラス参照を供給することで、1つ以上の拡張を宣言的に登録できます。

例えば、特定のテストメソッドのためにカスタムRandomParametersExtensionを登録するには、次のようにテストメソッドにアノテーションを付与します。

@ExtendWith(RandomParametersExtension.class)
@Test
void test(@Random int i) {
    // ...
}

特定のクラスとそのサブクラスの全てのテストにカスタムRandomParametersExtensionを登録するには、次のようにテストクラスにアノテーションを付与します。

@ExtendWith(RandomParametersExtension.class)
class MyTests {
    // ...
}

複数の拡張はこのように一緒に登録できます。

@ExtendWith({ FooExtension.class, BarExtension.class })
class MyFirstTests {
    // ...
}

他には、複数の拡張はこのように分けて登録できます。

@ExtendWith(FooExtension.class)
@ExtendWith(BarExtension.class)
class MySecondTests {
    // ...
}

ℹ️ 拡張登録の順序 @ExtendWithを通した宣言的に登録された拡張は、ソースコード内で宣言された順番で実行されます。例えば、MyFirstTestsMySecondTestsのテストの実行は、FooExtensionBarExtensionによって、正確にこの順番で拡張されます。

5.2.2. プログラム的な拡張登録

テストクラスのフィールドに@RegisterExtensionを付与することで、プログラム的に拡張を登録できます。

@ExtendWithを通して宣言的に拡張を登録した場合、概してアノテーション通りにのみ設定できます。それに対して、@RegisterExtensionを通して拡張を登録した場合、プログラム的に拡張することができます。例えば、拡張のコンストラクタや静的なファクトリメソッド、ビルダーAPIに引数を渡すために使うことができます。

ℹ️ @RegisterExtensionフィールドは、privatenull(評価時)であってはなりませんが、staticであっても非静的であっても構いません。

静的なフィールド

@RegisterExtensionフィールドがstaticである場合、@ExtendWithを通したクラスレベルでの拡張が登録された後に、拡張は登録されます。そのような静的な拡張は、拡張API内で実装できることを制限するものではありません。静的なフィールドを通して登録された拡張はそのため、BeforeEachCallbackのようなメソッドレベルの拡張APIと同様に、BeforeAllCallbackAfterAllCallbackTestInstancePostProcessorのようなクラスレベルとインスタンスレベルの拡張APIを実装します。

次の例では、テストクラスのserverフィールドが、WebServerExtensionによってサポートされているビルダーパターンを用いてプログラム的に初期化されます。設定されたWebServerExtensionは、クラスレベルの拡張として自動的に登録されます。例えば、クラスの全てのテストの実行前にサーバを起動し、実行完了後にサーバを停止するためです。さらに、@BeforeEach@AfterEach@Testメソッドだけでなく、@BeforeAll@AfterAllが付与された静的なライフサイクルメソッドは、必要であれば、serverフィールドを通して拡張のインスタンスにアクセスできます。

静的なフィールドを通して登録された拡張

class WebServerDemo {

    @RegisterExtension
    static WebServerExtension server = WebServerExtension.builder()
        .enableSecurity(false)
        .build();

    @Test
    void getProductList() {
        WebClient webClient = new WebClient();
        String serverUrl = server.getServerUrl();
        // Use WebClient to connect to web server using serverUrl and verify response
        assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());
    }
}

インスタンスフィールド

@RegisterExtensionフィールドが非静的(つまり、インスタンスフィールド)である場合、テストクラスが初期化された後、さらに、登録された各TestInstancePostProcessorがテストインスタンスを事後処理されてから(使われる拡張のインスタンスをアノテーションが付与されたフィールドに潜在的に挿入します)、拡張は登録されます。そのため、そのようなインスタンス拡張BeforeAllCallbackAfterAllCallbackTestInstancePostProcessorのようなクラスレベル、もしくはインスタンスレベルの拡張APIを実装している場合、それらのAPIは評価されません。デフォルトでは、@ExtendWithを通したメソッドレベルで登録された拡張の後に、インスタンス拡張は登録されます。しかしながら、テストクラスが@TestInstance(Lifecycle.PER_CLASS)セマンティックで設定されている場合、@ExtendWithを通したメソッドレベルの拡張が登録される前に、インスタンス拡張は登録されます。

次の例では、テストクラスのdocsフィールドが、カスタムlookUpDocsDir()メソッドを呼び出し、その結果をDocumentationExtensionの静的なforPath()ファクトリメソッドに供給することで、プログラム的に初期化されます。設定されたDocumentationExtensionは、メソッドレベルの拡張として自動的に登録されます。さらに、@BeforeEach@AfterEach@Testメソッドは、必要であれば、docsフィールドを通して拡張のインスタンスにアクセスできます。

インスタンスフィールドを通して登録された拡張

class DocumentationDemo {

    static Path lookUpDocsDir() {
        // return path to docs dir
    }

    @RegisterExtension
    DocumentationExtension docs = DocumentationExtension.forPath(lookUpDocsDir());

    @Test
    void generateDocumentation() {
        // use this.docs ...
    }
}

5.2.3. 自動的な拡張登録

アノテーションを用いた宣言的な拡張登録プログラム的な拡張登録のサポートに加えて、JUnit JupiterはJavaのjava.util.ServiceLoader機構を通じたグローバル拡張登録もサポートしており、サードパーティによる拡張を自動で検出し、クラスパス上で利用可能なものを元に自動で登録します。

特に、カスタム拡張は、同封するJARファイルの中で/META-INF/servicesフォルダ内のorg.junit.jupiter.api.extension.Extensionという名前のファイルで完全修飾クラス名を供給することによって、登録することができます。

自動拡張検出の有効化

自動検出は先進的な特徴で、そのためデフォルトでは有効化されていません。有効化するには、単にjunit.jupiter.extensions.autodetection.enabled設定パラメータtrueをセットするだけです。これは、JVMシステムプロパティやLauncherに渡されるLauncherDiscoveryRequest内の設定パラメータとして、またはJUnit Platform設定ファイル(詳細は設定パラメータをご覧ください)を通しても供給することが可能です。

例えば、拡張の自動検出を有効にするには、JVMを次のシステムプロパティで起動します。

-Djunit.jupiter.extensions.autodetection.enabled=true

自動検出が有効化されている場合、JUnit JUpiterのグローバル拡張(つまりTestInfoTestReporterなどへのサポート)の後に、ServiceLoader機構を通して発見された拡張は拡張レジストリに追加されます。

5.2.4. 拡張の継承

登録された拡張は、トップダウンに従ってテストクラス階層の中で継承されます。同様に、クラスレベルで登録された拡張は、メソッドレベルで継承されます。さらに、特定の拡張実装は、ある拡張コンテキストと親コンテキストに対して1度だけ登録されます。その結果として、重複する拡張実装の登録は無視されます。

5.3. 条件付きテスト実行

ExecutionConditionは、プログラム的な条件付きテスト実行のためのExtension APIを定義しています。

ExecutionConditionは、各コンテナ(つまり、テストクラス)が含む全てのテストを実行すべきか、供給されたExecutionContextに則って決定するために評価されます。同じようにExecutionConditionは、各テストごとに供給されたExecutionContextに則って実行されるか決定するために評価されます。

複数のExecutionConditionが登録されている場合、コンテナもしくはテストは、条件のうち1つでもdisabledを返した瞬間に無効化されます。そのため、条件が評価されるかどうかの保証はありません。なぜなら、他の条件が既にコンテナもしくはテストを無効化している可能性があるからです。つまり、評価は短絡ブーリアンORオペレータのように作動します。

具体的な例については、DisabledCondition@Disabledのソースコードをご覧ください。

5.3.1. 条件の無効化

時には、いくつかの条件を有効にしないでテストスイートを実行することが役に立ちます。例えば、@Disabledがたとえ付与されていたとしても、それらが壊れていないか確認するために実行したいかもしれません。このためには、単にjunit.jupiter.conditions.deactivate設定パラメータに現在のテスト実行でどの条件を無効化する(つまり、評価しない)かを指定するパターンを提供するだけです。パターンは、JVMシステムプロパティや、Launcherに渡されるLauncherDiscoveryRequest内の設定パラメータとして、もしくはJUnit Platform設定ファイル(詳細は設定パラメータをご覧ください)を通して供給することができます。

例えば、JUnitの@Disabled条件を無効化するには、次のシステムプロパティでJVMを起動します。

-Djunit.jupiter.conditions.deactivate=org.junit.*DisabledCondition

パターンマッチングの構文

junit.jupiter.conditions.deactivateパターンが、アスタリスク(*)のみで構成されている場合、全ての条件が無効化されます。そうでない場合、パターンは、各登録された条件の完全修飾クラス名(FQCN)とマッチしているかの判断に使われます。パターン内の全てのドット(.)は、FQCN内のドット(.)またはドル($)とマッチします。全てのアスタリスク(*)は、FQCN内の1つ以上の文字とマッチします。パターン内の他の全ての文字は、FQCNと1対1でマッチします。

例: - *:全ての条件を無効化します。 - org.junit.*org.junitパッケージとサブパッケージ以下の全ての条件を無効化します。 - *.MycCondition:クラス名がMyConditionのものを無効化します。 - *System*:クラス名にSystemを含むものを無効化します。 - org.example.MyCondition:FQCNがorg.example.MyConditionのものを無効化します。

5.4. テストインスタンスの事後処理

TestInstancePostProcessorは、テストインスタンスを事後処理したいExtensionsのAPIを定義しています。

一般的なユースケースには、テストインスタンスに依存関係を挿入したり、テストインスタンス上でカスタム初期化メソッドを呼び出すことなどが含まれます。

具体的な例については、MockitoExtensionSpringExtensionのソースコードをご覧ください。

5.5. パラメータの解決

ParameterResolverは、実行時にパラメータを動的に解決するためのExtensionAPIを定義しています。

テストコンストラクタまたは@Test@RepeatedTest@ParameterizedTest@TestFactory@BeforeEach@AfterEach@BeforeAll@AfterAllメソッドがパラメータを受け入れている場合、パラメータはParameterResolverによって実行時に解決される必要があります。ParameterResolverは、組み込みもの(TestInfoParameterResolverをご覧ください)まはたユーザが登録したものを使うことができます。一般的には、パラメータは名前アノテーション、それらの組み合わせで解決されます。具体的な例については、CustomTypeParameterResolverCustomAnnotationParameterResolverのソースコードをご覧ください。

⚠️ JDK 9より前のバージョンのJDKのjavacによって生成されるバイトコード内のバグによって、コアのjava.lang.reflect.ParameterAPIを通したパラメータに対するアノテーションの直接の探索は、内部クラスのコンストラクタ(例えば、@Nestedテストクラス内のコントラクタ)は常に失敗します。

ParameterResolver実装に供給されているParameterContextAPIは、そのため、パラメータ上のアノテーションの正しい探索のために、次の便利なメソッドを含んでいます。拡張に関する開発者(Extension Authors)は、JDK内のこのバグを避けるために、java.lang.reflect.Parameter内で提供されているメソッドの代わりに、これらのメソッドを使うことが強く奨励されています。 - boolean isAnnotated(Class<? extends Annotation> annotationType) - Optional<A> findAnnotation(Class<A> annotationType) - List<A> findRepeatableAnnotations(Class<A> annotationType)

5.6. テストライフサイクルのコールバック

次のインターフェイスは、テスト実行ライフサイクルにおいて様々なポイントでテストを拡張するためのAPIを定義しています。実例に関しては次の章を、さらなる詳細についてはorg.junit.jupiter.api.extensionパッケージ内の各インターフェイスのJavadocをご覧ください。

ℹ️ 複数の拡張APIsの実装 拡張に関する開発者は、これらのインターフェイスのうちいくつかを1つの拡張内に実装できます。具体的な例については、SpringExtensionのソースコードをご覧ください。

5.6.1. BeforeとAfterのテスト実行コールバック

BeforeAllCallbackAfterAllCallbackはそれぞれ、テストメソッドが実行される直前直後に実行される振る舞いを追加するためのExtensionAPIを定義しています。そのように、これらのコールバックは、タイミングよく跡を追う似たようなユースケースによく適しています。@BeforeEach@AfterEachメソッドの周りで呼び出されるコールバックを実装する必要がある場合、代わりにBeforeEachCallbackAfterEachCallbackを実装してください。

次の例は、これらのコールバックを使ってテストメソッドの実行時間を計算しログする方法を示しています。TimingExtensionは、テスト実行の時間計測とログのためにBeforeTestExecutionCallbackAfterTestExecutionCallbackを実装しています。

テストメソッドの実行を時間計測してログする拡張

import java.lang.reflect.Method;
import java.util.logging.Logger;

import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;

public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {

    private static final Logger logger = Logger.getLogger(TimingExtension.class.getName());

    private static final String START_TIME = "start time";

    @Override
    public void beforeTestExecution(ExtensionContext context) throws Exception {
        getStore(context).put(START_TIME, System.currentTimeMillis());
    }

    @Override
    public void afterTestExecution(ExtensionContext context) throws Exception {
        Method testMethod = context.getRequiredTestMethod();
        long startTime = getStore(context).remove(START_TIME, long.class);
        long duration = System.currentTimeMillis() - startTime;

        logger.info(() -> String.format("Method [%s] took %s ms.", testMethod.getName(), duration));
    }

    private Store getStore(ExtensionContext context) {
        return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod()));
    }

}

TimingExtensionTestsクラスは、@ExtendWithを通してTimingExtensionを登録しているので、そのテストは実行時にこの時間計測が適用されます。

TimingExtensionを用いるテストクラス

@ExtendWith(TimingExtension.class)
class TimingExtensionTests {

    @Test
    void sleep20ms() throws Exception {
        Thread.sleep(20);
    }

    @Test
    void sleep50ms() throws Exception {
        Thread.sleep(50);
    }

}

次にあるのは、TimingExtensionTestsが実行された時に生成されたログの例です。

INFO: Method [sleep20ms] took 24 ms.
INFO: Method [sleep50ms] took 53 ms.

5.7. 例外処理

TestExecutionExceptionHandlerは、テスト実行中に投げられた例外を処理するExtensionsのためのAPIを提供しています。

次の例は、IOExceptionの全インスタンスを飲み込み、他の型の例外を投げ直す拡張を示しています。

例外を扱う拡張

public class IgnoreIOExceptionExtension implements TestExecutionExceptionHandler {

    @Override
    public void handleTestExecutionException(ExtensionContext context, Throwable throwable)
            throws Throwable {

        if (throwable instanceof IOException) {
            return;
        }
        throw throwable;
    }
}

5.8. テストテンプレートに関する呼び出しコンテキストの提供

@TestTemplateメソッドは、少なくとも1つのTestTemplateInvocationContextProviderが登録されているときにだけ実行されます。各プロバイダは、TestTemplateInvocationContextインスタンスのStreamを提供する責務を担っています。各コンテキストは、カスタム表示名と@TestTemplateメソッドの次の呼び出しのために使われる追加的な拡張のリストを特定します。

次の例は、テストテンプレートの書き方だけでなく、TestTemplateInvocationContextProviderの登録・実装の方法も示しています。

付随する拡張を持つテストテンプレート

@TestTemplate
@ExtendWith(MyTestTemplateInvocationContextProvider.class)
void testTemplate(String parameter) {
    assertEquals(3, parameter.length());
}

public class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {
    @Override
    public boolean supportsTestTemplate(ExtensionContext context) {
        return true;
    }

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
        return Stream.of(invocationContext("foo"), invocationContext("bar"));
    }

    private TestTemplateInvocationContext invocationContext(String parameter) {
        return new TestTemplateInvocationContext() {
            @Override
            public String getDisplayName(int invocationIndex) {
                return parameter;
            }

            @Override
            public List<Extension> getAdditionalExtensions() {
                return Collections.singletonList(new ParameterResolver() {
                    @Override
                    public boolean supportsParameter(ParameterContext parameterContext,
                            ExtensionContext extensionContext) {
                        return parameterContext.getParameter().getType().equals(String.class);
                    }

                    @Override
                    public Object resolveParameter(ParameterContext parameterContext,
                            ExtensionContext extensionContext) {
                        return parameter;
                    }
                });
            }
        };
    }
}

この例では、テストテンプレートは2回呼び出されます。呼び出しの表示名は、呼び出しコンテキストによって決められたように"foo"と"bar"になります。各呼び出しは、メソッドパラメータを解決するためのカスタムParameterResolverを登録します。ConsoleLauncherを使った時の出力は次のようになります。

└─ testTemplate(String) ✔
   ├─ foo ✔
   └─ bar ✔

TestTemplateInvocationContextProvider拡張APIは、異なるコンテキストでテストのようなメソッドを繰り返し呼び出すことを当てにしている別種のテストを実装することを第一に意図しています。例えば、異なるパラメータを用いたり、テストクラスインスタンスを異なる方法で準備したり、コンテキストを修正せずに複数回行うなどです。機能に提供ためにこの拡張ポイントを使う繰り返しテストまたはパラメータ化テストの実装を参照してください。

5.9. 拡張での状態の保持

通常、拡張は一度だけインスタンス化されます。そのため、関連する質問が挙がります:拡張のある呼び出しからの状態は、どのようにして次の呼び出しに保持するのか?ExtensionContextAPIは、まさにこの目的のためのStoreを提供します。拡張は、後からの収集のためにストアに値を入れます。メソッドレベルのスコープでのStoreの使用例は、タイミング拡張をご覧ください。テスト実行中にExtensionContextに貯蔵された値は、周囲のExtensionContextでは利用できないことに注意してください。ExtensionContextはネストされているかもしれないので、内部コンテキストのスコープもまた限定的となっています。Storeを通した値の貯蔵と収集に利用可能なメソッドの詳細については、対応するJavaDocをご覧ください。

ℹ️ ExtensionContext.Store.CloseableResource 拡張コンテキストのストアは、その拡張コンテキストのライフサイクルにバインドされています。拡張コンテキストのライフサイクルが終了するとき、関連するストアも閉じます。全ての貯蔵された値は、CloseableResourceのインスタンスで、close()メソッドの呼び出しによって通知されます。

5.10. 拡張でサポートしているユーティリティ

junit-platform-commonsアーティファクトは、アノテーションやクラス、リフレクション、タスクをスキャンするクラスパスに作動する保守されたユーティリティメソッドを含むorg.junit.platform.commons.supportという名前のパッケージを公開しています。TestEngineExtensionの開発者は、JUnit Platformの動作に合わせるため、これらサポートされたメソッドを利用することが推奨されています。

5.10.1. アノテーションのサポート

AnnotationSupportは、静的なユーティリティメソッドを提供しており、アノテーションの付与された要素(つまり、パッケージやアノテーション、クラス、インターフェイス、メソッド、フィールド)を操作できます。これらは、ある要素に特定のアノテーションが付与またはメタ付与されているか確認したり、特定のアノテーションを検索したり、クラスやインターフェイス内でアノテーション付与されているメソッドとフィールドを探し出すためのメソッドが含まれています。これらのメソッドのいくつかは、実装されたインターフェイスとクラス階層内をアノテーションを見つけるために捜索します。さらなる詳細については、AnnotationSupportのJavaDocをご覧ください。

5.10.2. クラスのサポート

ClassSupportは、クラス(つまり、java.lang.Classのインスタンス)に作動する静的なユーティリティメソッドを提供しています。さらなる詳細については、ClassSupportのJavaDocをご覧ください。

5.10.3. リフレクションのサポート

ReflectionSupportは、標準のJDKリフレクションとクラス読み込み機構を増強するための静的なユーティリティメソッドを提供しています。これらは、特定の述語にマッチするクラスを探すためにクラスパスをスキャンしたり、クラスの新しいインスタンスを読み込んで生成したり、メソッドを見つけて呼び出すためのメソッドを含みます。これらのメソッドのいつくかは、マッチするメソッドの位置を特定するためにクラス階層を走査します。さらなる詳細については、ReflectionSupportのJavaDocをご覧ください。

5.11. ユーザコードと拡張の相対実行順序

1つ以上のテストメソッドを含むテストクラスを実行する時、ユーザの提供するテストとライフサイクルに関するメソッドに加えて、いくつかの拡張コールバックが呼び出されます。次の図は、ユーザ提供のコードと拡張コードの相対順序を示しています。

ユーザコードと拡張コード

ユーザ提供のテストとライフサイクルに関するメソッドはオレンジで示され、拡張によって提供されるコールバックは青で示されています。灰色のボックスは、1つのテストメソッドの実行を意味しており、テストクラスの全てのテストメソッドに対して繰り返されます。

次の表は、ユーザコードと拡張コードの図式内の12のステップについて、より詳しく説明をしています。

ステップ インターフェイス/アノテーション 説明
1 インターフェイス org.junit.jupiter.api.extension.BeforeAllCallback コンテナの全てのテストが実行される前に実行される拡張コード
2 アノテーション org.junit.jupiter.api.BeforeAll コンテナの全てのテストが実行される前に実行されるユーザコード
3 インターフェイス org.junit.jupiter.api.extension.BeforeEachCallback 各テストが実行される前に実行される拡張コード
4 アノテーション org.junit.jupiter.api.BeforeEach 各テストが実行される前に実行されるユーザコード
5 インターフェイス org.junit.jupiter.api.extension.BeforeTestExecutionCallback テストが実行される直前に実行される拡張コード
6 アノテーション org.junit.jupiter.api.Test 実際のテストメソッドとなるユーザコード
7 インターフェイス org.junit.jupiter.api.extension.TestExecutionExceptionHandler テスト中に投げられる例外を扱う拡張コード
8 インターフェイス org.junit.jupiter.api.extension.AfterTestExecutionCallback テストと対応する例外ハンドラが実行された直後に実行される拡張コード
9 アノテーション org.junit.jupiter.api.AfterEach 各テストが実行された後に実行されるユーザコード
10 インターフェイス org.junit.jupiter.api.extension.AfterEachCallback 各テストが実行された後に実行される拡張コード
11 アノテーション org.junit.jupiter.api.AfterAll コンテナの全てのテストが実行された後に実行されるユーザコード
12 インターフェイス org.junit.jupiter.api.extension.AfterAllCallback コンテナの全てのテストが実行された後に実行される拡張コード

最も単純なケースでは、実際のテストメソッドのみが実行されます(ステップ 6)。他の全てのステップはオプションで、対応するライフサイクルコールバックをサポートするユーザコードまたは拡張コードの存在に依存します。様々なライフサイクルコールバックのさらなる詳細については、各アノテーションと拡張それぞれのJavaDocをご覧ください。

JUnit 4からの移行

JUnit JUpiterのプログラミングモデルと拡張モデルは、JUnit 4のRulesRunnersといった特徴をネイティブではサポートしていませんが、ソースコード保守運用者が全ての既存のテストやテスト拡張、カスタムされたビルド・テスト基盤をJUnit JUpiterに移行するためにアップデートする必要があることは期待していません。

代わりに、JUnitは、JUnit Platformの基盤を用いてJUnit 3とJUnit 4ベースのテストを実行できるJUnit Vintageテストエンジンを通して、寛大な移行経路を提供しています。JUnit JUpiterに特化したクラスとアノテーションは全て新しいorg.junit.jupiterベースパッケージ以下にあるので、クラスパス上にあるJUnit 4とJUnit Jupiterはいずれもコンフリクトを起こしません。そのため、JUnit JUpiterテストと一緒に既存のJUnit 4のテストを保守することは安全です。さらに、JUnitチームはJUnit 4.xベースラインの保守とバグ修正に関するリリースを提供し続けるので、開発者はスケジュール上ではJUnit Jupiter移行のための時間が多くあります。

6.1. JUnit Platform上でのJUnit 4テストの実行

junit-vintage-engineアーティファクトがテストの実行時のパスにあるか確認するだけです。そのような場合、JUnit 3とJUnit 4のテストは、JUnit Platformラウンチャーによって自動的に拾われます。

GradleとMavenでの実施方法は、jnit5-samplesレポジトリのプロジェクト例をご覧ください。

6.1.1. カテゴリのサポート

@Categoryが付与されたテストクラスとメソッドに関して、JUnit Vintageテストエンジンはカテゴリの完全修飾クラス名を対応するテスト識別子のタグとして扱います。例えば、テストメソッドに@Category(Example.class)が付与されている場合、"com.acme.Example"のタグが付与されます。JUnit 4のCategoriesランナーと同じように、この情報は発見されたテストが実行される前にフィルタリングされるのに使われます(詳細についてはテストを実行するをご覧ください)。

移行のためのTips

次は、既存のJUnit 4のテストをJUnit Jupiterに移行する際に気をつけるべきことです。

6.3. JUnit 4のRuleの限定的なサポート

上で述べたように、JUnit Jupiterは、JUnit 4のRuleをネイティブではサポートしていませんし、これからもしません。しかしながら、JUnitチームは、多くの組織(特に、大きい組織)がカスタムしたRuleを利用した巨大なJUnit 4のコードベースを持っていることを理解しています。これらの組織に奉仕し、段階的な移行を可能にするために、JUnitチームは、言葉通り選択的ですが、いくつかJUnit 4のRuleをJUnit Jupiter内でサポートすることを決めました。このサポートは、アダプタベースで行われ、JUnit Jupiterの拡張モデルと意味的に互換のあるRule(つまり、テスト全体の実行フローを完全には変更しないもの)に限定されています。

JUnit Jupiterのjunit-jupiter-migrationsupportモジュールは、現在のところ、次の3つのRule型とそれらのサブクラスをサポートしています。

JUnit 4と同様に、Ruleが付与されたフィールドもメソッド同様にサポートされています。これらを使うことで、レガシーコード内のRule実装のようなテストクラスに対するクラスレベルの拡張を、JUnit 4のRuleのインポート文含めて変更なしで残すことができます。

この限られた形でのRuleサポートは、クラスレベルのアノテーションorg.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupportによって切り替えできます。このアノテーションは、合成アノテーションで、全ての移行をサポートする拡張(VerifierSupportExternalResourceSupportExpectedExceptionSupport)を有効にします。

しかしながら、JUnit 5に対する新たな拡張を開発する際には、JUnit 4のRuleベースのモデルの代わりに、JUnit Jupiterの新しい拡張モデルを使ってください。

⚠️ Junit Jupiter内におけるJUnit 4のRuleのサポートは現在のところ、実験的な特徴です。詳細については、実験的なAPIをご覧ください。

7. 先進的なトピック

7.1. JUnit PlatformラウンチャーAPI

JUnit 5の主要な目的の一つは、JUnitとそのプログラム的なクライアント(ビルドツールとIDE)間のインターフェイスをより強固で安定したものにすることです。その目的は、テスト発見と実行の内部を、外界から必要とされる全てのフィルタリングと設定から切り離すことです。

JUnit 5は、テスト発見・フィルタリング・実行に使うことのできるLauncherのコンセプトを導入します。さらに、サードパーティによるテストライブラリ(SpockやCucumber、FitNesse)は、カスタムTestEngineを提供することによってJUnit Platformの起動基盤にプラグインできます。

起動APIはjunit-platform-launcherモジュールにあります。

起動APIの使用例は、junit-platform-consoleプロジェクトのConsoleLauncherにあります。

7.1.1. テストの発見

テストディレクトリをプラットフォーム自身専用の特徴として導入することは、過去にテストクラスやテストメソッドを特定するのに経験しなければならなかった困難のほとんどからIDEやビルドツールを(上手くいけば)解放するでしょう。

使用例:

import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;

import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
    .selectors(
        selectPackage("com.example.mytests"),
        selectClass(MyTestClass.class)
    )
    .filters(
        includeClassNamePatterns(".*Tests")
    )
    .build();

Launcher launcher = LauncherFactory.create();

TestPlan testPlan = launcher.discover(request);

(訳注:原典では分割されていますが、上2つのソースコード片は同一のファイルかと思われます。)

現在のところ、クラスとメソッド、あるパッケージ内の全てのクラスを選択することや、クラスパス内の全てのテストを探索することさえ可能です。発見は、全ての関係するテストエンジンで行われます。

結果としてTestPlanは、LauncherDiscoveryRequestに適合する全てのエンジンやクラス、テストメソッドの階層的(かつ、読み込み専用)な記述となります。クライアントは、そのツリーを走査し、ノードに関する詳細を集め、元ソース(クラスやメソッド、ファイルの位置)へのリンクを入手できます。テストプランの全てのノードは、ユニークなIDを持っており、特定のテストやテストグループを呼び出すのに使用できます。

7.1.2. テストの実行

テストを実行するために、クライアントは、発見フェイズと同じLauncherDiscoveryRequestを使うか、新しいリクエストを生成できます。テスト進行とレポートは、次の例のようにLauncheに1つ以上のTestExecutionListener実装を登録することで達成できます。

LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
    .selectors(
        selectPackage("com.example.mytests"),
        selectClass(MyTestClass.class)
    )
    .filters(
        includeClassNamePatterns(".*Tests")
    )
    .build();

Launcher launcher = LauncherFactory.create();

// Register a listener of your choice
TestExecutionListener listener = new SummaryGeneratingListener();
launcher.registerTestExecutionListeners(listener);

launcher.execute(request);

execute()メソッドに返り値はありませんが、リスナーを用いることで、容易に自身のオブジェクト内で最終結果を集約することができます。例については、SummaryGeneratingListenerをご覧ください。

7.1.3. 自身のテストエンジンをプラグイン

JUnitは現在のところ、2つの利用可能なTestEngineを提供しています。 - junit-jupiter-engine:JUnit Jupiterのコアです。 - junit-vintage-engine:JUnit 4の上部に位置する薄いレイヤーで、古いテストを起動基盤で実行できるようにします。

サードパーティもまた、junit-platform-engineモジュール内のインターフェイスを実装してエンジンを登録することで、自身のTestEngineに貢献する可能性があります。エンジンの登録は、現在のところ、Javaのjava.util.ServiceLoader機構によってサポートされています。例えば、junit-jupiter-engineモジュールは、junit-jupiter-engineJAR中の/META-INF/services内にあるorg.junit.platform.engine.TestEngineという名前のファイルのorg.junit.jupiter.engine.JupiterTestEngineを登録します。

7.1.4. 自身のテスト実行リスナーをプラグイン

テスト実行リスナーをプログラム的に登録するためのパブリックなLauncherAPIメソッドに加えて、Javaのjava.util.ServiceLoader機能を通して実行時に発見されるカスタムTestExecutionListener実装が自動的にDefaultLauncherを用いて登録されます。例えば、TestExecutionListenerを実装し、/META-INF/services/org.junit.platform.launcher.TestExecutionListenerファイル内で宣言されているexample.TestInfoPrinterクラスは、自動的に読み込まれ登録されます。

8. APIの進化

JUnit 5の大きな目標の1つは、多くのプロジェクトに現在進行形で使われていますが、JUnitを進化させるために保守運用者ができることを改善させることです。JUnit 4では、元は内側の構成物として加えられていた多くのものが、外部の拡張開発者やツールビルダーに使われました。そのことが、JUnit 4を特に難しく、時には不可能なものにしました。

というわけで、JUnit 5は全ての公開されている利用可能なインターフェイスやクラス、メソッドに対して、あらかじめ定義したライフサイクルを導入します。

8.1. APIのバージョンとステータス

全ての公開されたアーティファクトは、バージョン番号 <メジャー>.<マイナー>.<パッチ>を持ち、全ての公開されている利用可能なインターフェイスまたはクラス、メソッドは、@API Guardianプロジェクトから@APIを付与されています。このアノテーションのstatus属性は、次の値のうちの1つを割り当てできます。

ステータス 説明
INTERNAL JUnit自身以外のコードから使われてはなりません。事前通知なしに削除される恐れがあります。
DEPRECATED もう使うべきではありません。次のマイナーリリースで消滅する恐れがあります。
EXPERIMENTAL フィードバックを求めている新しい実験的な機能を意図しています。この要素は注意を持って使ってください。MAINTAINEDSTABLEに将来的には昇格する可能性がありますが、事前通知なしにパッチでも削除される恐れがあります。
MAINTAINED 少なくとも現在のメジャーバージョンにおける次のマイナー・リリースまでは後方非互換に変更されない機能を意図しています。削除予定の場合は、まずDEPRECATEDに降格されます。
STABLE 現在のメジャーバージョン(5.*)では後方非互換に変更されない機能を意図しています。

@APIアノテーションが型に存在していた場合、その型の全てのパブリックなメンバーにも同様に適用されると考えられます。メンバーは、それより低い安定性の異なるstatus値を宣言することが許されています。

8.2. 実験的なAPI

次の表は、(@API(status = EXPERIMENTAL)を通して)実験的なものとして現在のところ指定されているAPIを列挙しています。これらのAPIを使うときは注意してください。

パッケージ名 クラス名 タイプ
org.junit.jupiter.api AssertionsKt クラス
org.junit.jupiter.api DynamicContainer クラス
org.junit.jupiter.api DynamicNode クラス
org.junit.jupiter.api DynamicTest クラス
org.junit.jupiter.api TestFactory アノテーション
org.junit.jupiter.api.condition DisabledIf アノテーション
org.junit.jupiter.api.condition EabledIf アノテーション
org.junit.jupiter.api.extension ScriptEvaluationException クラス
org.junit.jupiter.params ParameterizedTest アノテーション
org.junit.jupiter.params.aggregator AggregateWith アノテーション
org.junit.jupiter.params.aggregator ArgumentAccessException クラス
org.junit.jupiter.params.aggregator ArgumentsAccessor インターフェイス
org.junit.jupiter.params.aggregator ArgumentsAggregationException クラス
org.junit.jupiter.params.aggregator ArgumentsAggregator インターフェイス
org.junit.jupiter.params.converter ArgumentConversionException クラス
org.junit.jupiter.params.converter ArgumentConverter インターフェイス
org.junit.jupiter.params.converter ConvertWith アノテーション
org.junit.jupiter.params.converter JavaTimeConversionPattern アノテーション
org.junit.jupiter.params.converter SimpleArgumentConverter クラス
org.junit.jupiter.params.provider Arguments インターフェイス
org.junit.jupiter.params.provider ArgumentsProvider インターフェイス
org.junit.jupiter.params.provider ArgumentsSource アノテーション
org.junit.jupiter.params.provider ArgumentsSources アノテーション
org.junit.jupiter.params.provider CsvFileSource アノテーション
org.junit.jupiter.params.provider CsvSource アノテーション
org.junit.jupiter.params.provider EnumSource アノテーション
org.junit.jupiter.params.provider MethodSource アノテーション
org.junit.jupiter.params.provider ValueSource アノテーション
org.junit.jupiter.params.support AnnotationConsumer インターフェイス
org.junit.platform.engine.discovery ModuleSelector クラス

8.3. @API ツールのサポート

@API Guardianプロジェクトは、@APIの付与されたAPIの開発者と利用者のためのツールサポートを提供することを計画しています。例えば、ツールサポートは、JUnit APIが@APIアノテーションの宣言に応じて使われているかを確認する手段を提供する見込みです。

9. 貢献者

GitHub上の現在の貢献者リストを直接ご覧ください。

10. リリースノート

リリースノートはこちらで利用可能です。