1. 概要

このドキュメントは、実際にテストを書くプログラマーや拡張機能の作者、テストエンジンの作者、それからビルドツールや IDE ベンダーのための包括的なリファレンスです。

1.1. JUnit 5 とは何か

過去の JUnit と違って、JUnit 5 は3種類のサブプロジェクトが提供するさまざまなモジュールを組み合わせたソフトウェアになっています。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform は JVM で テストフレームワークを起動する ための基盤を提供します。 Platform で実行するテストフレームワークを開発するための TestEngine API も公開しています。 さらに、コマンドラインから Platform を起動するための Console Launcher や、JUnit 4 の環境で任意の TestEngine を実行するための JUnit 4 based Runner を提供します。 JUnit Platform を最上級の機能として対応している IDE もたくさんあります(IntelliJ IDEA, Eclipse, NetBeans, Visual Studio Code)。 ビルドツールについても同様です(Gradle, Maven, Ant)。

JUnit Jupiter は JUnit 5 でテストを書いたり、JUnit 5 の拡張機能を作成するための新しい プログラミングモデル拡張モデル を組み合わせたものです。 Jupiter サブプロジェクトは、Platform で Jupiter のテストを実行するための TestEngine を提供します。

JUnit Vintage は Platform で JUnit 3 や JUnit 4 に基づくテストを実行するための TestEngine を提供します。 クラスパスあるいはモジュールパスに JUnit 4.12 より新しいバージョンの jar ファイルを配置しなければなりません。

1.2. 対応している Java のバージョン

JUnit 5 を実行するには Java 8 より新しいバージョンが必要です。 しかし、Java 8 より古いバージョンの JDK でコンパイルしたテストコードも実行できます。

1.3. 分からないことの調べ方

JUnit 5 に関する質問は Stack Overflow や JUnit チームの Gitter へ投稿するといいでしょう。

1.4. JUnit 5 の始め方

1.4.1. JUnit のアーティファクトをダウンロードする

あなたのプロジェクトに追加するべきアーティファクトを調べるには、 依存ライブラリに関するメタデータ を参照してください。 依存ライブラリとして追加するには ビルドツールでテストを実行するプロジェクト例 を参照してください。

1.4.2. JUnit 5 の機能

JUnit 5 で利用できる機能や使い方を調べるには、このユーザーガイドの対応するセクションを読むといいでしょう。 次のようなトピックで分類しています。

1.4.3. プロジェクト例

Junit 5 のサンプルプロジェクトリポジトリ には、そのまま実行できるサンプルプロジェクトがあるので、自分の環境にコピーして実験してみてください。 JUnit Jupiter を使用するプロジェクトだけでなく、JUnit Vintage や他のテストフレームワークを使用するプロジェクトもあります。 ビルドスクリプトも参考になるでしょう(build.gradlepom.xml など)。 ビルドツールやプログラミング言語の組み合わせが特徴的なプロジェクトのリンクは次のとおりです。

2. テストを作成する

次のコード例は、JUnit Jupiter で作成した最小限のテストです。 この章では全ての機能を説明していきます。

最小限のテストケース
import static org.junit.jupiter.api.Assertions.assertEquals;

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class MyFirstJUnitJupiterTests {

    private final Calculator calculator = new Calculator();

    @Test
    void addition() {
        assertEquals(2, calculator.add(1, 1));
    }

}

2.1. アノテーション

JUnit Jupiter はテストを書いたりフレームワークを拡張するためのアノテーションを提供しています。

明記しない限り、全ての主要なアノテーションは junit-jupiter-api モジュールの org.junit.jupiter.api パッケージにあります。

アノテーション 内容

@Test

そのメソッドがテストメソッドであることを示します。JUnit 4 の @Test と違って、何も属性が定義されていません。JUnit Jupiter ではテストの振る舞いを拡張する個別のアノテーションを提供しています。オーバーライド しなかったメソッドは 継承 します。

@ParameterizedTest

そのメソッドが パラメタライズドテスト であることを示します。オーバーライド しなかったメソッドは 継承 します。

@RepeatedTest

そのメソッドが 繰り返し実行するテスト のテンプレートであることを示します。オーバーライド しなかったメソッドは 継承 します。

@TestFactory

そのメソッドが 動的に生成するテスト のファクトリメソッドであることを示します。オーバーライド しなかったメソッドは 継承 します。

@TestTemplate

そのメソッドが テストケースのテンプレート であることを示します。登録した テンプレートプロバイダー の返す呼び出しコンテキストの数に応じて、複数回実行するために設計します。オーバーライド しなかったメソッドは 継承 します。

@TestClassOrder

@Nested で修飾した テストクラスの実行順序 を変更します。アノテーションは 継承 します。

@TestMethodOrder

このアノテーションで修飾したクラスにおける テストメソッドの実行順序 を変更します。JUnit 4 の @FixMethodOrder と同じように機能します。アノテーションは 継承 します。

@TestInstance

このアノテーションで修飾したクラスにおけるインスタンスのライフサイクル を変更します。アノテーションは 継承 しません。

@DisplayName

テストクラスやテストメソッドに独自の 表示名 を設定します。アノテーションは 継承 しません。

@DisplayNameGeneration

テストクラスに独自の 表示名生成ロジック を設定します。アノテーションは 継承 します。

@BeforeEach

当該テストクラスについて、@Test, @RepeatedTest, @ParameterizedTest, @TestFactory で修飾されたメソッドを実行する それぞれの タイミングで、このメソッドを 先に 実行することを保証します。JUnit 4 では @Before を使用していました。オーバーライド しなかったメソッドは 継承 します。

@AfterEach

当該テストクラスについて、@Test, @RepeatedTest, @ParameterizedTest, @TestFactory で修飾されたメソッドを実行する それぞれの タイミングで、このメソッドを 後から 実行することを保証します。JUnit 4 では @Before を使用していました。オーバーライド しなかったメソッドは 継承 します。

@BeforeAll

当該テストクラスについて、@Test, @RepeatedTest, @ParameterizedTest, @TestFactory で修飾された 全ての メソッドより 先に 、このメソッドを実行することを保証します。JUnit 4 では @BeforeClass を使用していました。このアノテーションで修飾したメソッドは 隠蔽 されたり オーバーライド されたりしなければ 継承 します。また、当該クラスが「クラス単位(per-class)」で インスタンスのライフサイクル を管理していないなら、このアノテーションで修飾するメソッドは static で宣言しなければなりません。

@AfterAll

当該テストクラスについて、@Test, @RepeatedTest, @ParameterizedTest, @TestFactory で修飾された 全ての メソッドより 後に 、このメソッドを実行することを保証します。JUnit 4 では @AfterClass を使用していました。このアノテーションで修飾したメソッドは 隠蔽 されたり オーバーライド されたりしなければ 継承 します。また、当該クラスが「クラス単位(per-class)」で インスタンスのライフサイクル を管理していないなら、このアノテーションで修飾するメソッドは static で宣言しなければなりません。

@Nested

その内部クラスが ネストテストクラス であることを示します。「クラス単位(per-class)」で インスタンスのライフサイクル を管理していないなら、ネストテストクラスには @BeforeAll@AfterAll で修飾したメソッドを定義できません。アノテーションは 継承 しません。

@Tag

クラスやメソッドのいずれかに、 テストを選択するためのタグ を定義します。TestNG におけるテストグループ、JUnit 4 におけるカテゴリーに相当します。このアノテーションでクラスを修飾した場合は 継承 します。メソッドの場合は 継承 しません。

@Disabled

そのクラスやメソッドが 無効 であることを示します。JUnit 4 では @Ignore を使用していました。アノテーションは 継承 しません。

@Timeout

@Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @TestTemplate で修飾されたメソッドの実行時間が指定した時間を超過したら失敗させます。アノテーションは 継承 します。

@ExtendWith

クラスを修飾して、 登録する拡張機能を宣言的に記述 します。アノテーションは 継承 します。

@RegisterExtension

フィールドを修飾して、 登録する拡張機能を手続き的に記述 します。フィールドは 隠蔽 されない限り 継承 します。

@TempDir

フィールドや、テストメソッドおよびライフサイクルメソッドの引数へ、 一時ディレクトリ の情報を注入します。パッケージは org.junit.jupiter.api.io です。

一部のアノテーションは 実験的な機能 です。詳しい状態は 実験的な API を参照してください。

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

JUnit Jupiter のアノテーションは メタアノテーション として使用できます。 つまり、自分で定義した 合成アノテーション は、使用したメタアノテーションのセマンティクスを自動的に 継承 できるのです。

例えば、コードベースのあちこちに @Tag("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 {
}

@Fast アノテーションは次のように使用できます。

@Fast
@Test
void myFastTest() {
    // ...
}

もう一歩先に進めて、 @Tag("fast")@Test を置き換える @FastTest アノテーションを定義できます。

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;
import org.junit.jupiter.api.Test;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@Test
public @interface FastTest {
}

JUnit は @FastTest アノテーションで修飾したメソッドを、@Test アノテーションと "fast" タグを指定したメソッドとして自動的に認識します。

@FastTest
void myFastTest() {
    // ...
}

2.2. テストクラスとテストメソッド

テストクラス: 1つ以上の テストメソッド を含む任意のトップレベルクラス、static 宣言したメンバークラス、ネストテストクラス

抽象クラスはテストクラスにできません。 また、テストクラスに定義できるコンストラクタは1つだけです。

テストメソッド: @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @TestTemplate のいずれかのアノテーションで直接、あるいは、間接的に修飾されたインスタンスメソッド

ライフサイクルメソッド: @BeforeAll, @AfterAll, @BeforeEach, @AfterEach のいずれかのアノテーションで直接、あるいは、間接的に修飾されたインスタンスメソッド

テストメソッドとライフサイクルメソッドは、基底クラスから継承することもできますし、インターフェイスから継承することもできます(テストインターフェイスとデフォルトメソッド を参照)。 なお、どちらのメソッドも抽象メソッドにできませんし、返り値も宣言できません(ただし @TestFactory メソッドには返り値が必要です)。

クラスとメソッドの可視性

テストクラス、テストメソッド、ライフサイクルメソッドの可視性は public でなくても構いませんが、privateしてはいけません

他のパッケージのテストクラスを継承する場合など、技術的な必要性がない限り、基本的にはテストクラスやテストメソッドの可視性に public を指定しないほうがいいでしょう。 JPMS(Java Platform Module System)のモジュールパスを単純化したい場合は、可視性を public にする理由になるでしょう。

次のコード例は @Test メソッドと全てのライフサイクルメソッドを使用したものです。 実行時のセマンティクスについては テストの実行順序コールバックによる包み隠す振る舞い を参照してください。

基本的なテストクラス
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

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
    }

    @Test
    void abortedTest() {
        assumeTrue("abc".contains("Z"));
        fail("test should have been aborted");
    }

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}

2.3. 表示名

テストクラスやテストメソッドには @DisplayName で空白文字や特殊文字や絵文字などを含む独自の表示名を宣言できます。 表示名はテストレポートや IDE で実行したテストランナーで使用できます。

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

}

2.3.1. 表示名生成ロジック

JUnit Jupiter は @DisplayNameGeneration アノテーションで独自の表示名生成ロジックを使用できます。 ただし、DisplayNameGenerator の生成した表示名より、@DisplayName アノテーションで宣言した値を優先して使用します。

表示名生成ロジックを作成するときは DisplayNamaGenerator インターフェイスを実装します。 Jupiter の提供する既存の実装は次のとおりです。

DisplayNameGenerator の実装クラス 振る舞い

Standard

JUnit Jupiter 5.0 で導入された標準的な表示名生成ロジックです。

Simple

引数無しテストメソッドの括弧を除去します。

ReplaceUnderscores

アンダースコアを空白文字で置換します。

IndicativeSentences

エンクロージングクラスの名前と、テストクラスやテストメソッドの名前を連結して完全な文章を生成します。

IndicativeSentences については、区切り文字と内部で使用する表示名生成ロジックを @IndicativeSentencesGeneration アノテーションで設定できます。

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.IndicativeSentencesGeneration;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class DisplayNameGeneratorDemo {

    @Nested
    @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
    class A_year_is_not_supported {

        @Test
        void if_it_is_zero() {
        }

        @DisplayName("A negative value for year is not supported by the leap year computation.")
        @ParameterizedTest(name = "For example, year {0} is not supported.")
        @ValueSource(ints = { -1, -4 })
        void if_it_is_negative(int year) {
        }

    }

    @Nested
    @IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class)
    class A_year_is_a_leap_year {

        @Test
        void if_it_is_divisible_by_4_but_not_by_100() {
        }

        @ParameterizedTest(name = "Year {0} is a leap year.")
        @ValueSource(ints = { 2016, 2020, 2048 })
        void if_it_is_one_of_the_following_years(int year) {
        }

    }

}
+-- DisplayNameGeneratorDemo [OK]
  +-- A year is not supported [OK]
  | +-- A negative value for year is not supported by the leap year computation. [OK]
  | | +-- For example, year -1 is not supported. [OK]
  | | '-- For example, year -4 is not supported. [OK]
  | '-- if it is zero() [OK]
  '-- A year is a leap year [OK]
    +-- A year is a leap year -> if it is divisible by 4 but not by 100. [OK]
    '-- A year is a leap year -> if it is one of the following years. [OK]
      +-- Year 2016 is a leap year. [OK]
      +-- Year 2020 is a leap year. [OK]
      '-- Year 2048 is a leap year. [OK]

2.3.2. 表示名生成ロジックの初期値を変更する

junit.jupiter.displayname.generator.default という 設定パラメータ で、JUnit Jupiter が DisplayNameGenerator の初期値として使用するクラスの完全修飾クラス名を指定できます。 @DisplayNameGeneration アノテーションで指定するのと同じように、DisplayNameGenerator インターフェイスの実装クラスを指定します。 エンクロージングクラスやインターフェイスを @DisplayNameGeneration アノテーションで修飾しない限り、常に DisplayNameGenerator の初期値を使用します。 そして、@DisplayName アノテーションで宣言した値は常に優先されます。

例えば、表示名生成ロジックの初期値として ReplaceUnderscores を使うには、プロパティファイル(例えば src/test/resources/junit-platform.properties)へ次のように記述します。

junit.jupiter.displayname.generator.default = \
    org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores

自分で作成した DisplayNameGenerator インターフェイスの実装クラスも同じように指定できます。

まとめると、テストクラスやテストメソッドの表示名は次の優先順位に従って決定されます。

  1. @DisplayName アノテーションで宣言した値

  2. @DisplayNameGeneration で宣言した DisplayNameGenerator の返す値

  3. プロパティフィルで指定した DisplayNameGenerator の初期値が返す値

  4. org.junit.jupiter.api.DisplayNameGenerator.Standard が返す値

2.4. アサーション

JUnit Jupiter は大量のアサーションメソッドを提供します。 JUnit 4 でも提供していたメソッドに加えて、それらのメソッドを Java 8 のラムダ式で使えるようにしたメソッドが増えています。 全てのアサーションメソッドは 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 java.util.concurrent.CountDownLatch;

import example.domain.Person;
import example.util.Calculator;

import org.junit.jupiter.api.Test;

class AssertionsDemo {

    private final Calculator calculator = new Calculator();

    private final Person person = new Person("Jane", "Doe");

    @Test
    void standardAssertions() {
        assertEquals(2, calculator.add(1, 1));
        assertEquals(4, calculator.multiply(2, 2),
                "The optional failure 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 all
        // failures will be reported together.
        assertAll("person",
            () -> assertEquals("Jane", 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("e"))
                );
            },
            () -> {
                // 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() {
        Exception exception = assertThrows(ArithmeticException.class, () ->
            calculator.divide(1, 0));
        assertEquals("/ by zero", 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.
            new CountDownLatch(1).await();
        });
    }

    private static String greeting() {
        return "Hello, World!";
    }

}
予防的なタイムアウトを assertTimeoutPreemptively() で記述する

宣言的タイムアウト と逆に、Assertions クラスの assertTimeoutPreemptively() メソッドは指定された executable あるいは supplier を、呼び出し元とは別のスレッドで実行します。 そのため、executablesupplierjava.lang.ThreadLocal を使用するコードを実行すると、予期せぬ副作用を引き起こす可能性があります。

典型的な例の1つとして、Spring Framework の提供するトランザクション管理機能が挙げられます。 特に、テストメソッドを実行する前のトランザクション状態を ThreadLocal へ記録している場合に問題になります。 つまり、assertTimeoutPreemptively() メソッドに渡した executable あるいは supplier の実行する Spring のコンポーネントがトランザクションで行ったあらゆる操作を、テストの管理しているトランザクションとしてロールバックできないのです。 たとえテストの管理しているトランザクションをロールバックしても、コミットした結果は永続化されてしまいます。

ThreadLocal を使用している他のフレームワークでも同じような副作用に悩まされるでしょう。

2.4.1. Kotlin 用のアサーション

JUnit Jupiter は Kotlin から使用するためのアサーションメソッドを提供しています。 全てのアサーション関数は org.junit.jupiter.api パッケージのトップレベル関数として定義されています。

import example.domain.Person
import example.util.Calculator
import java.time.Duration
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.assertTimeout
import org.junit.jupiter.api.assertTimeoutPreemptively

class KotlinAssertionsDemo {

    private val person = Person("Jane", "Doe")
    private val people = setOf(person, Person("John", "Doe"))

    @Test
    fun `exception absence testing`() {
        val calculator = Calculator()
        val result = assertDoesNotThrow("Should not throw an exception") {
            calculator.divide(0, 1)
        }
        assertEquals(0, result)
    }

    @Test
    fun `expected exception testing`() {
        val calculator = Calculator()
        val exception = assertThrows<ArithmeticException> ("Should throw an exception") {
            calculator.divide(1, 0)
        }
        assertEquals("/ by zero", exception.message)
    }

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

    @Test
    fun `grouped assertions from a stream`() {
        assertAll("People with first name starting with J",
            people
                .stream()
                .map {
                    // This mapping returns Stream<() -> Unit>
                    { assertTrue(it.firstName.startsWith("J")) }
                }
        )
    }

    @Test
    fun `grouped assertions from a collection`() {
        assertAll("People with last name of Doe",
            people.map { { assertEquals("Doe", it.lastName) } }
        )
    }

    @Test
    fun `timeout not exceeded testing`() {
        val fibonacciCalculator = FibonacciCalculator()
        val result = assertTimeout(Duration.ofMillis(1000)) {
            fibonacciCalculator.fib(14)
        }
        assertEquals(377, result)
    }

    @Test
    fun `timeout exceeded with preemptive termination`() {
        // The following assertion fails with an error message similar to:
        // execution timed out after 10 ms
        assertTimeoutPreemptively(Duration.ofMillis(10)) {
            // Simulate task that takes more than 10 ms.
            Thread.sleep(100)
        }
    }
}

2.4.2. 外部のアサーションライブラリ

Junit Jupiter はたいていのテストを満足するアサーションの部品を提供していますが、マッチャー のようにより強力で便利な機能が必要になる場合もあります。 そういう場合、JUnit チームとしては AssertJHamcrestTruth などの外部ライブラリを使用することをお勧めしています。 開発者は自由にアサーションライブラリを選択できるのです。

例えば、マッチャー を組み合わせたり、フルーエント API を使うことで、可読性や説明力の高いアサーションを表現できるようになります。 JUnit 4 の org.junit.Assert クラスが提供する assertThat() は、 Hamcrest のマッチャー を組み合わせて使用できるようになっていました。 しかし、JUnit Jupiter の org.junit.jupiter.api.Assertions クラスは assertThat() を提供していないため、開発者は外部アサーションライブラリの提供するマッチャーを使わなければなりません。

次のコード例では、JUnit Jupiter のテストで Hamcrest の提供する assertThat() を使っています。 Hamcrest の jar ファイルがクラスパスに存在するなら、assertThat(), is(), equalTo() などの static メソッドを import して、assertWithHamcrestMatcher() テストメソッドで使用できます。

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

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class HamcrestAssertionsDemo {

    private final Calculator calculator = new Calculator();

    @Test
    void assertWithHamcrestMatcher() {
        assertThat(calculator.subtract(4, 1), is(equalTo(3)));
    }

}

JUnit 4 のプログラミングモデルで作成したテストなら引き続き org.junit.Assert#assertThat メソッドを使用できます。

2.5. アサンプション(仮定)

JUnit Jupiter では、JUnit 4 で提供していたアサンプションメソッドの一部と、それらのメソッドを Java 8 のラムダ式でメソッド参照として使用できるようにしたメソッドを提供しています。 全てのアサンプションメソッドは 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 example.util.Calculator;

import org.junit.jupiter.api.Test;

class AssumptionsDemo {

    private final Calculator calculator = new Calculator();

    @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, calculator.divide(4, 2));
            });

        // perform these assertions in all environments
        assertEquals(42, calculator.multiply(6, 7));
    }

}
JUnit Jupiter 5.4 から JUnit 4 の org.junit.Assume クラスのメソッドをアサンプションメソッドとして使えるようになりました。 例えば、JUnit 4 の AssumptionViolatedException で、本来失敗とするべきテストを中断できるようになっています。

2.6. テストを無効にする

条件に基づくテスト実行 で紹介した @Disabled アノテーションで修飾したり、独自の ExecutionCondition を作成したりすることで、テストクラス全体あるいは一部のテストメソッドだけを 無効 にできます。

次のコード例では @Disabled でテストクラスを無効にしています。

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

@Disabled("Disabled until bug #99 has been fixed")
class DisabledClassDemo {

    @Test
    void testWillBeSkipped() {
    }

}

次のコード例では @Disabled でテストメソッドを無効にしています。

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

class DisabledTestsDemo {

    @Disabled("Disabled until bug #42 has been resolved")
    @Test
    void testWillBeSkipped() {
    }

    @Test
    void testWillBeExecuted() {
    }

}
理由(reason) を指定せずに @Disabled を記述する場合もあると思います。 しかし、JUnit チームとしては、簡単でいいからテストクラスやテストメソッドを無効にする理由を書いたほうがいいと考えています。 コード例では @Disabled("Disabled until bug \#42 has been resolved") のように理由を書いています。 開発チームによっては、課題管理ツールに登録した課題を自動的に追跡できるよう、課題番号を書いている場合もあるそうです。

2.7. 条件に基づくテスト実行

JUnit Jupiter の ExecutionCondition API を使うと、手続き的に評価した条件 に応じて、テストコンテナやテストの 有効無効 を制御できます。 組み込みの ExecutionCondition の中でも分かりやすいのは、@Disabled アノテーション(テストを無効にする)に対応する DisabledCondition です。 org.junit.jupiter.api.condition パッケージには、@Disabled 以外のさまざまなアノテーションに対応する ExecutionCondition があるため、開発者は 宣言的に テストクラスやテストメソッドの 有効無効 を制御できます。 複数の ExecutionCondition を登録した場合、どれか1つでも条件を満たせばテストコンテナやテストは直ちに 無効 になります。 組み込みの ExecutionCondition に対応する全てのアノテーションには disabledReason という属性があるため、テストを無効にした理由を提供できます。

ExecutionCondition や、次のセクションも参照してください。

合成アノテーション

次のセクションで説明している、条件として使用できる 全てのアノテーションは、合成アノテーション を作るためのメタアノテーションとして使用できます。 例えば、 @EnabledOnOs デモ では @Test@EnabledOnOs を組み合わせて単一の再利用可能なアノテーション @TestOnMac の作り方を説明しています。

特別な記載がない限り、次のセクションで説明している 条件として使用できる 全てのアノテーションは1度しか指定できません。 直接的に指定するだけでなく、継承やメタアノテーションにより間接的に指定する場合も含めて、JUnit は最初に発見したアノテーションだけを使用します。 他のアノテーションは無視されてしまうのです。 しかし、org.junit.jupiter.api.condition パッケージには、他のアノテーションと組み合わせたアノテーションもあるので注意してください。

2.7.1. OS の条件指定

@EnabledOnOs@DisabledOnOs を使うと、指定した OS でテストコンテナやテストの有効と無効を制御できます。

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

2.7.2. Java 実行環境(JRE:Java Runtime Environment) の条件指定

@EnabledOnJre@DisabledOnJre を使うと、指定した JRE でテストコンテナやテストの有効と無効を制御できます。 @EnabledForJreRange@DisabledForJreRange を使うと、JRE のバージョンを範囲指定できます。 範囲指定の最小値(min)の初期値は JRE.JAVA_8 で、最大値(max)の初期値は JRE.OTHER です。 範囲は半開区間として評価します。

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

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

@Test
@EnabledForJreRange(min = JAVA_9, max = JAVA_11)
void fromJava9to11() {
    // ...
}

@Test
@EnabledForJreRange(min = JAVA_9)
void fromJava9toCurrentJavaFeatureNumber() {
    // ...
}

@Test
@EnabledForJreRange(max = JAVA_11)
void fromJava8To11() {
    // ...
}

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

@Test
@DisabledForJreRange(min = JAVA_9, max = JAVA_11)
void notFromJava9to11() {
    // ...
}

@Test
@DisabledForJreRange(min = JAVA_9)
void notFromJava9toCurrentJavaFeatureNumber() {
    // ...
}

@Test
@DisabledForJreRange(max = JAVA_11)
void notFromJava8to11() {
    // ...
}

2.7.3. システムプロパティの条件指定

@EnabledIfSystemProperty@DisabledIfSystemProperty を使うと、指定したシステムプロパティでテストコンテナやテストの有効と無効を制御できます。 matches 属性に指定した値は正規表現として評価します。

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

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

JUnit Jupiter 5.6 から、@EnabledIfSystemProperty@DisabledIfSystemProperty繰り返し可能なアノテーション になりました。 その結果、これらのアノテーションはテストクラスやテストメソッドに複数回指定できるようになりました。 直接的に指定するだけでなく、継承やメタアノテーションにより間接的に指定する場合もあるでしょう。

2.7.4. 環境変数の条件指定

@EnabledIfEnvironmentVariable@DisabledIfEnvironmentVariable を使うと、指定したシステムプロパティでテストコンテナやテストの有効と無効を制御できます。 matches 属性に指定した値は正規表現として評価します。

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

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

JUnit Jupiter 5.6 から、@EnabledIfEnvironmentVariable@DisabledIfEnvironmentVariable繰り返し可能なアノテーション になりました。 その結果、これらのアノテーションはテストクラスやテストメソッドに複数回指定できるようになりました。 直接的に指定するだけでなく、継承やメタアノテーションにより間接的に指定する場合もあるでしょう。

2.7.5. 独自の条件指定

@EnabledIf@DisabledIf を使うと、指定した名前のメソッドを実行したときの返り値に応じて、テストコンテナやテストの有効と無効を制御できます。 指定するメソッドには単一の引数 ExtensionContext を渡すことができます。

@Test
@EnabledIf("customCondition")
void enabled() {
    // ...
}

@Test
@DisabledIf("customCondition")
void disabled() {
    // ...
}

boolean customCondition() {
    return true;
}

一方、テストクラス以外の場所で定義されたメソッドを指定することもできます。 メソッド名は 完全修飾名 で指定しなければなりません。

package example;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;

class ExternalCustomConditionDemo {

    @Test
    @EnabledIf("example.ExternalCondition#customCondition")
    void enabled() {
        // ...
    }

}

class ExternalCondition {

    static boolean customCondition() {
        return true;
    }

}
テストクラスに @EnabledIf@EnabledIf を指定するときに指定できるメソッドは static 宣言しなければなりません。 テストクラス以外の場所で定義されたメソッドを指定するとして、そのメソッドも static 宣言しなければなりません。 それ以外の場合は static 宣言したメソッドと、インスタンスメソッドのどちらでも利用できます。

2.8. タグ付けと選択

テストクラスやテストメソッドには @Tag アノテーションでタグ付けが可能です。 タグは テストを検出して実行 するときにテストを選択するための条件に使用できます。 タグ セクションでは JUnit Platform のタグ機能について説明しています。

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

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

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

}
タグ付けのためのアノテーションを作成する方法については メタアノテーションと合成アノテーション を参照してください。

2.9. テストの実行順序

初期設定では、テストクラスやテストメソッドの実行順序が、意図的に曖昧にした決定論的なアルゴリズムで決まるようになっています。 テストスイートが実行するテストクラスやテストメソッドの順番は変わらないことが保証されているため、繰り返し可能なビルド(repeatable build)を推進してくれます。

テストクラステストメソッド の定義については テストクラスとテストメソッド を参照してください。

2.9.1. テストメソッドの実行順序

本物の ユニットテスト は普通ならどのような順番で実行しても同じ結果になるべきだと言われています。 しかし、どうしても実行する順番を指定しなければならないこともあります。 例えば、統合テスト機能テスト を作成するときは順番が重要です。 特に @TestInstance(Lifecycle.PER_CLASS) も組み合わせているならなおのことです。

テストメソッドを実行する順番を制御するには、テストクラスやテストインターフェイスを @TestMethodOrder アノテーションで修飾し、適切な MethodOrderer の実装を指定します。 MethodOrderer インターフェイスを自分で実装することもできますし、組み込みの実装を使うこともできます。

次のコード例は @Order アノテーションでテストメソッドの実行順序を指定しています。

import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {

    @Test
    @Order(1)
    void nullValues() {
        // perform assertions against null values
    }

    @Test
    @Order(2)
    void emptyValues() {
        // perform assertions against empty values
    }

    @Test
    @Order(3)
    void validValues() {
        // perform assertions against valid values
    }

}
テストメソッドの実行順序の初期値を変更する

junit.jupiter.testmethod.order.default という 設定パラメータ で、JUnit Jupiter が初期値として使用する MethodOrderer の実装クラスの完全修飾クラス名を指定できます。 エンクロージングクラスやインターフェイスを @TestMethodOrder アノテーションで修飾しない限り、常に MethodOrderer の初期値を使用します。 そして、@TestMethodOrder アノテーションで指定した順序決定ルールは常に優先されます。

初期値を MethodOrderer.OrderAnnotation へ変更するには、プロパティファイル(例えば src/test/resources/junit-platform.properties)へ次のように記述します。

junit.jupiter.testmethod.order.default = \
    org.junit.jupiter.api.MethodOrderer$OrderAnnotation

自分で作成した MethodOrderer インターフェイスの実装クラスも同じように指定できます。

2.9.2. テストクラスの実行順序

普通ならテストクラスをどのような順番で実行しても同じ結果になるべきだと言われています。 しかし、どうしても実行する順番を指定しなければならないこともあります。 テストクラス間に意図しない依存関係は存在しないことを保証するため、テストクラスをランダムな順序で実行したい場合もあるでしょう。 また、ビルド時間を最適化するため、実行順序を指定したい場合もあるでしょう。

  • 前回失敗したテストを先に実行したい: フェールファストモード

  • 並列実行できるなら時間のかかるテストを先に実行したい: テスト実行時間最小化モード

  • それ以外のさまざまなユースケース

テストスイートの 大域的な テストクラスの実行順序は、junit.jupiter.testclass.order.default という 設定パラメータ で、JUnit Jupiter が初期値として使用する ClassOrderer の実装クラスの完全修飾クラス名を指定できます。

自分で作成した ClassOrderer インターフェイスの実装クラスも同じように指定できます。

次のコード例は @Order アノテーションで テストクラス の実行順序を指定しています。 初期値を ClassOrderer.OrderAnnotation へ変更するには、プロパティファイル(例えば src/test/resources/junit-platform.properties)へ次のように記述します。

junit.jupiter.testclass.order.default = \
    org.junit.jupiter.api.ClassOrderer$OrderAnnotation

ClassOrder は、全てのトップレベルテストクラス(static 宣言したネストテストクラスも含む)と @Nested で修飾したネストテストクラスに指定できます。

トップレベルテストクラスの順序は同じトップレベルテストクラス同士で比較します。同じエンクロージングクラスのネストテストクラスの順序は、同じエンクロージングクラスのネストテストクラス同士で比較します。

ネストテストクラスの 局所的な 実行順序を指定するには、エンクロージングクラスを @TestClassOrder で修飾します。 そして @TestClassOrder アノテーションに ClassOrder を実装したクラスのクラス参照を直接指定します。 ClassOrder は再帰的にネストテストクラスを探索します。 @TestClassOrder の局所的な宣言は、基底クラスから継承した @TestClassOrder の宣言で上書きされます。 また、設定パラメータ junit.jupiter.testclass.order.default で大域的に宣言した ClassOrder で上書きされます。

次のコード例は @Order アノテーションで ネストテストクラス の実行順序を指定しています。

import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestClassOrder;

@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class OrderedNestedTestClassesDemo {

    @Nested
    @Order(1)
    class PrimaryTests {

        @Test
        void test1() {
        }
    }

    @Nested
    @Order(2)
    class SecondaryTests {

        @Test
        void test2() {
        }
    }
}

2.10. テストインスタンスのライフサイクル

それぞれのテストメソッドが独立して実行できるよう、そして、変更可能なテストインスタンスの状態に基づく予期せぬ副作用を無くすため、JUnit は テストメソッドテストクラスとテストメソッド)を実行するたびに、テストクラスの新しいインスタンスを作るようにしました。 この、メソッド単位(per-method)のインスタンスライフサイクル管理は、JUnit Jupiter の基本的な振る舞いであり、過去の全ての JUnit と同じものです。

メソッド単位(per-method)のインスタンスライフサイクル管理が有効になっているなら、テストメソッドが 無効 になっても、テストクラスのインスタンスは作成するので注意してください(条件に基づくテスト実行@Disabled アノテーションなど)。

JUnit Jupiter に同じインスタンスで全てのテストメソッドを実行させたいときは、テストクラスを @TestInstance(Lifecycle.PER_CLASS) で修飾しましょう。 そうすると、テストクラスのインスタンスを1度しか作らなくなります。 テストメソッドがインスタンス変数に記録した状態へ依存しているなら、@BeforeEach@AfterEach のメソッドで状態を初期化しなければなりません。

クラス単位(per-class)のインスタンスライフサイクル管理にはメソッド単位(per-method)にない利点があります。 それは、@BeforeAll@AfterAll のメソッドに static 宣言が不要になることです(同様に、インターフェイスの default メソッドも不要になります)。 クラス単位(per-class)のインスタンスライフサイクル管理では、ネストテストクラスで @BeforeAll@AfterAll を使うこともできます。

Kotlin でテストを書いているときも、クラス単位(per-class)のインスタンスライフサイクル管理 にしたほうが、@BeforeAll@AfterAll をメソッドで実装できるので便利でしょう。

2.10.1. インスタンスライフサイクル管理の初期値を変更する

テストクラスやテストインターフェイスを @TestInstance で修飾していないとき、JUnit Jupiter は 既定 のインスタンスライフサイクル管理をします。 本来の 初期値PER_METHOD ですが、テスト実行計画全体で変更できます。 インスタンスライフサイクル管理の初期値を変更するには、junit.jupiter.testinstance.lifecycle.default という 設定パラメータ に、TestInstance.Lifecycle の列挙値を設定します(大文字小文字の違いは無視します)。 JVM のシステムプロパティや、Launcher に渡す LauncherDiscoveryRequest設定パラメータ 、JUnit Platform の設定ファイル(設定パラメータの構成 を参照)で指定することもできます。

システムプロパティでインスタンスライフサイクル管理の初期値に Lifecycle.PER_CLASS を設定するには次のように指定します。

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

いろいろ指定する方法あありますが、JUnit Platform の設定ファイルで指定するのが最も確実な方法です。 JUnit Platform の設定ファイルはソースコードリポジトリに格納してバージョン管理して、IDE やビルドツールから利用できるようにするといいでしょう。、

JUnit Platform の設定ファイルでインスタンスライフサイクル管理の初期値に Lifecycle.PER_CLASS を設定するには次のように記述します(例えば rsc/test/resources/junit-platform.properties)。

junit.jupiter.testinstance.lifecycle.default = per_class
インスタンスライフサイクル管理の初期値を不適切な値に変更すると、テストの結果が予測不能になったり、不安定になったりします。 例えば、ビルドツールの初期値をクラス単位(per-class)にしたのに、IDE ではメソッド単位(per-method)でテストを実行していると、ビルドジョブで解析の難しいエラーが発生します。 それゆえに、システムプロパティではなく JUnit Platform の設定ファイルで初期値を変更することをお勧めします。

2.11. ネストテストクラス

@Nested アノテーションを使うと、いろいろなグループのテストの関係性をより分かりやすく表現できるようになります。 Java の内部クラスでテストの階層構造を考えられるようになるからです。 次のコード例をIDEで実行した結果は次のとおりです。

内部クラスで構成したスタック型データ構造のテストスイート
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());
            }
        }
    }
}

このテストを IDE で実行すると、実行結果は同じような木構造になります。

writing tests nested test ide
Executing a nested test in an IDE

この場合、内側のテストで階層的なライフサイクルメソッドを定義することで、外側のテストの事前条件を内側のテストで使用しているのが分かります。 createNewStack()@BeforeEach で修飾されたライフサイクルメソッドで、メソッドを定義したクラスと、その内側のクラスのテストメソッドを実行するときに使用します。

外側のテストのセットアップは内側のテストより先に実行するため、結果として全てのテストを独立して実行できることになります。 内側のテストを実行するときは常に外側のテストのセットアップを実行するので、内側のテストだけを実行できるのです。

@Nested で修飾できるのは static でない内部クラス(インナークラス) だけです。 どれだけネストが深くなったとしても内部クラスか1つの例外を除いて完全なライフサイクルに対応しています。 例外とは、@BeforeAll@AfterAll初期設定 では動作しないことです。 Java の内部クラスは static メンバを定義できないからです。 ただし、ネストテストクラスを @TestInstance(Lifecycle.PER_CLASS) で修飾すればその例外を回避できます(テストインスタンスのライフサイクル を参照)。

2.12. コンストラクタやメソッドによる依存性の注入

過去の JUnit ではテストクラスのコンストラクタやテストメソッドに引数を定義できませんでした(少なくとも標準の Runner ではそうでした)。 JUnit Jupiter における重要な変更点の1つは、テストクラスのコンストラクタやテストメソッドに引数を定義できるようになったことです。 優れた柔軟性をもたらすだけでなく、依存性の注入 ができるようになったのです。

ParameterResolver は JUnit の拡張機能が実行時に 動的に パラメータを解決するための API を定義しています。 ParameterResolver を登録しておくと、実行時に テストクラス のコンストラクタや テストメソッド あるいは ライフサイクルメソッド の引数を解決してくれるのです(テストクラスとテストメソッド を参照)。

現在は次の3種類の組み込みリゾルバーが自動的に登録されるようになっています。

  • TestInfoParameterResolver: コンストラクタやメソッドに TestInfo 型の引数があれば、TestInfoParameterResolver が今のテストコンテナあるいはテストの情報を持つ TestInfo のインスタンスを設定します。 設定された TestInfo から、今のテストコンテナやテストの情報(例えば表示名、テストクラス名、テストメソッド名、割り当てられたタグなど)を取得できます。 表示名はクラス名やメソッド名、あるいは、@DisplayName で指定された名前になります。

    TestInfo は JUnit 4 の TestName ルールを置き換える部品として使用できます。 次のコード例ではテストクラスのコンストラクタと @BeforeEach メソッド、テストメソッドに 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() {
    }

}
  • RepetitionInfoParameterResolver: @RepeatedTest, @BeforeEach, @AfterEach で修飾したメソッドに RepetitionInfo 型の引数があれば、RepetitionInfoParameterResolverRepetitionInfo のインスタンスを設定します。 設定された RepetitionInfo から、今の繰り返し情報と @RepeatedTest に指定された繰り返し数の合計値を取得できます。 @RepeatedTest の外側のコンテキストでは RepetitionInfoParameterResolver を登録しないので注意してください。繰り返しテストの具体例 も参照してください。

  • TestReporterParameterResolver: コンストラクタやメソッドに TestReporter 型の引数があれば, TestReporterParameterResolverTestReporter のインスタンスを設定します。 設定された TestReporter で、実行しているテストに関するデータを公開できます。 公開したデータは TestExecutionListenerreportingEntryPublished() メソッドで取得できるので、IDE に表示したりレポートに出力したりできます。

    JUnit 4 で標準出力と標準エラー出力へテストの結果を出力していた場合、JUnit Jupiter では TestReporter を使わなければいけません。 テストクラスを @RunWith(JUnitPlatform.class) で修飾すると、全ての出力を標準出力へ書き込むようになります。 また、IDE によっては、テストの結果を標準出力と UI の両方に出力します。

class TestReporterDemo {

    @Test
    void reportSingleValue(TestReporter testReporter) {
        testReporter.publishEntry("a status message");
    }

    @Test
    void reportKeyValuePair(TestReporter testReporter) {
        testReporter.publishEntry("a key", "a value");
    }

    @Test
    void reportMultipleKeyValuePairs(TestReporter testReporter) {
        Map<String, String> values = new HashMap<>();
        values.put("user name", "dk38");
        values.put("award year", "1974");

        testReporter.publishEntry(values);
    }

}
他のパラメータリゾルバーを使いたければ適切な拡張機能を @ExtendWith で登録しなければなりません。

ParameterResolver の実装例として RandomParametersExtension を見てみましょう。 拡張モデルとパラメータ解決処理を説明するため、短く、分かりやすくしています。 また、MyRandomParametersTest でその使い方を説明します。

@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 のソースコードを参照してください。

作成した ParameterResolver が引数の型だけで値を注入するかどうかを判断するなら、総称型の TypeBasedParameterResolver を基底クラスにするといいでしょう。 supportsParameters メソッドが対応する引数の型の情報を隠蔽します。

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

JUnit Jupiter ではインターフェイスのデフォルトメソッドを @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @TestTemplate, @BeforeEach, @AfterEach で修飾できるようになりました。 インターフェイスやクラスを @TestInstance(Lifecycle.PER_CLASS)テストインスタンスのライフサイクル を参照)で修飾しているなら、static メソッドやインターフェイスのデフォルトメソッドを @BeforeAll, @AfterAll で修飾できます。

@TestInstance(Lifecycle.PER_CLASS)
interface TestLifecycleLogger {

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

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

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

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

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

}
interface TestInterfaceDynamicTestsDemo {

    @TestFactory
    default Stream<DynamicTest> dynamicTestsForPalindromes() {
        return Stream.of("racecar", "radar", "mom", "dad")
            .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))));
    }

}

テストインターフェイスの実装クラスは、テストインターフェイスを修飾した @ExtendWith, @Tag を自動的に継承します。 テスト実行の前、および、後のコールバック の実装例を、TimingExtension のソースコードで確認しましょう。

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

これらのインターフェイスをテストクラスで実装すると次のようになります。

class TestInterfaceDemo implements TestLifecycleLogger,
        TimeExecutionLogger, TestInterfaceDynamicTestsDemo {

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

}

TestInterfaceDemo を実行すると次のような出力が得られるでしょう。

INFO  example.TestLifecycleLogger - Before all tests
INFO  example.TestLifecycleLogger - About to execute [dynamicTestsForPalindromes()]
INFO  example.TimingExtension - Method [dynamicTestsForPalindromes] took 19 ms.
INFO  example.TestLifecycleLogger - Finished executing [dynamicTestsForPalindromes()]
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

この機能を応用して、インターフェイスを契約とするテストを作成できます。 例えば、Object.equalsComparable.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 returnsPositiveNumberWhenComparedToSmallerValue() {
        T value = createValue();
        T smallerValue = createSmallerValue();
        assertTrue(value.compareTo(smallerValue) > 0);
    }

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

}

それぞれのインターフェイスをテストクラスで実装すると、それぞれのテストを継承することになります。 もちろん、インターフェイスメソッドは実装しなければなりません。

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

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

    @Override
    public String createSmallerValue() {
        return "apple"; // 'a' < 'b' in "banana"
    }

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

}
これらのコード例は説明用で、完全なものではありません。

2.14. 繰り返しテスト

JUnit Jupiter では @RepeatedTest でテストを繰り返し実行する回数、あるいは、繰り返し回数の最大値を指定してテストを実行できるようになりました。 繰り返しごとに実行するテストメソッドは、@Test で修飾した通常のテストメソッドと完全に同じライフサイクルと拡張機能に対応しています。

次のコード例では、repeatedTest() を10回繰り返すようになっています。

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

繰り返し実行する回数を指定するだけでなく、name 属性で表示名を変更できるようになっています。 表示名は、文字列リテラルとプレースホルダを組み合わせたパターン文字列です。 現在対応しているプレースホルダは次のとおりです。

  • {displayName}: @RepeatedTest で修飾したメソッド名

  • {currentRepetition}: 現在の繰り返し回数

  • {totalRepetitions}: 繰り返し回数の最大値

表示名の初期値は "repetition {currentRepetition} of {totalRepetitions}" です。 従って、前のコード例では繰り返しごとの表示名は repetition 1 of 10repetition 2 of 10 のようになります。 それぞれの表示名にテストメソッド名を含めたい場合は、独自のパターン文字列を指定するか、定義済みの RepeatedTest.LONG_DISPLAY_NAME を指定するといいでしょう。 RepeatedTest.LONG_DISPLAY_NAME"{displayName} :: repetition {currentRepetition} of {totalRepetitions}" と同じ内容で、repeatedTest() :: repetition 1 of 10repeatedTest() :: repetition 2 of 10 のように出力されます。

@RepeatedTest, @BeforeEach, @AfterEach で修飾したメソッドの内部から、現在の繰り返し回数や、繰り返し回数の最大値を取得したい場合、メソッド引数に RepetitionInfo 型の引数を宣言し、インスタンスを注入させることができます。

2.14.1. 繰り返しテストの具体例

繰り返しテストのさまざまな使い方を RepeatedTestsDemo クラスで説明していきます。

repeatedTest() メソッドの使い方は前のセクションで説明したのと同じです。 repeatedTestWithRepetitionInfo() メソッドは RepetitionInfo でメソッドの中から繰り返し回数などの情報を取得しています。

customDisplayName() メソッドは @DisplayName@RepeatedTest で変更した表示名を TestInfo から取得して、生成された名前の形式が合っているか検証しています。 Repeat! はプレースホルダの {displayName}@DisplayName の値で置換したものです。 1/1 はプレースホルダの {currentRepetition}/{totalRepetitions} を置換したものです。 逆に customDisplayNameWithLongPattern() メソッドでは表示名のパターンとして RepeatedTest.LONG_DISPLAY_NAME を使っています。

repeatedTestInGerman() メソッドは繰り返しテストで表示名をローカライズする方法を示しています。 ここではドイツ語へ翻訳しているので、Wiederholung 1 von 5, Wiederholung 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("Repeat! 1/1", testInfo.getDisplayName());
    }

    @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME)
    @DisplayName("Details...")
    void customDisplayNameWithLongPattern(TestInfo testInfo) {
        assertEquals("Details... :: repetition 1 of 1", testInfo.getDisplayName());
    }

    @RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}")
    void repeatedTestInGerman() {
        // ...
    }

}

ユニコードテーマを有効にした ConsoleLauncher を使うと次のような出力が得られるでしょう。

├─ 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... {two-colons} 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 ✔

2.15. パラメタライズテスト

パラメタライズテストは複数の異なる引数でテストメソッドを実行する機能です。 テストメソッドを @Test ではなく @ParameterizedTest で修飾します。 メソッドの引数を提供する ソース を少なくとも1つは宣言しなければなりません。 テストメソッドは ソース の提供するそれぞれの値を引数として 消費 します。

次のコード例では @ValueSourceString の配列を与えています。

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

パラメタライズテストを実行すると、それぞれのテストメソッドの呼び出しを別々に報告します。 例えば、ConsoleLauncher は次のように出力するでしょう。

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

2.15.1. 必要な準備

パラメタライズテストを実行するには、依存ライブラリに junit-jupiter-params を追加しなければなりません。 詳しくは 依存ライブラリに関するメタデータ を参照してください。

2.15.2. 引数を消費するということについて

パラメタライズテストでは基本的に指定した ソース から直接値を 消費 します。 このとき、ソース から取得した値の(配列の)添え字と、メソッド引数の順序の対応関係は1対1になります(@CsvSource を参照)。 しかし、ソース から取得した値を 集約 して、メソッド引数の1つに渡すこともできます(引数の集約 を参照)。 また、メソッド引数には ParameterResolver が注入する引数(例えば TestInfoTestReporter など)を宣言できます。 パラメタライズテストにおけるメソッド引数の宣言規則は次のとおりです。

  • 0 個以上の 添え字に対応する引数 は先頭で宣言しなければなりません。

  • 0 個以上の 集約する引数 はその次に宣言しなければなりません。

  • 0 個以上の ParameterResolver が注入する引数は最後に宣言しなければなりません。

「添え字に対応する引数」とは、ArgumentsProvider の提供する Arguments に対する添え字のことで、添え字に対応する引数へ値を渡します。 「集約する引数」とは、ArgumentAccessor 型の引数か、@AggregateWith で修飾した引数のことです。

AutoCloseable 型の引数について

java.lang.AutoCloseable やその派生インターフェイスの java.io.Closeable を実装した型の引数は、@AfterEach メソッドを実行した後、自動的に閉じます。 また、テストメソッドから AfterEachCallback 拡張を呼び出した場合は、テストメソッドを実行した後、自動的に閉じます。

この振る舞いを無効化するには @ParameterizedTestautoCloseArguments 属性に false を設定します。 特に、AutoCloseable を実装した引数を同じパラメタライズテストメソッドで何回も再利用するなら、毎回閉じられないように設定しておきましょう。

2.15.3. 引数の源

JUnit Jupiter はいくつか ソース のアノテーションを同梱しています。 このセクションではそれぞれのアノテーションについて簡単な使い方を説明してきます。 詳細は org.junit.jupiter.params.provider パッケージの Javadoc を参照してください。

@ValueSource

@ValueSource はソースアノテーションの中でも簡単なものの1つです。 配列やリテラル値を、1引数のテストメソッドに渡すことができます。

@ValueSource の対応しているリテラルのデータ型は次のとおりです。

  • short

  • byte

  • int

  • long

  • float

  • double

  • char

  • boolean

  • java.lang.String

  • java.lang.Class

例えば、次のコード例では 1, 2, 3 それぞれを引数として @ParameterizedTest で修飾したテストメソッドを実行します。

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

コーナーケースや 不正な入力 を与えられたときの振る舞いを確かめるには、パラメタライズテストで null空値 を渡せるようにすると便利です。 1引数のテストメソッドへ null や空値を渡すためのアノテーションには次のようなものがあります。

  • @NullSource: @ParameterizedTest で修飾したメソッドの引数に null を渡します。

    • プリミティブ型の引数には使用できません

  • @EmptySource: @ParameterizedTest で修飾したメソッドの引数に 空値 を渡します。対応している引数の型は参照型(java.lang.String, java.util.List, java.util.Set, java.util.Map)と配列型(int[], char[][] など)です。

    • これらのデータ型から派生した型には未対応です。

  • @NullAndEmptySource: @NullSource@EmptySource合成アノテーション で、両方の使い方ができます。

パラメタライズテストへ 空白 に相当するさまざま文字列を渡したいときは、@ValueSource を使うといいでしょう。 具体的には @ValueSource(strings = {" ", "   ", "\t", "\n"}) のように記述できます。

次のコード例のように、@NullSource@EmptySource@ValueSource を組み合わせると、null空値空白 を幅広く入力させることができます。

@ParameterizedTest
@NullSource
@EmptySource
@ValueSource(strings = { " ", "   ", "\t", "\n" })
void nullEmptyAndBlankStrings(String text) {
    assertTrue(text == null || text.trim().isEmpty());
}

合成アノテーションの @NullAndEmptySource を使うと少し短くなります。

@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = { " ", "   ", "\t", "\n" })
void nullEmptyAndBlankStrings(String text) {
    assertTrue(text == null || text.trim().isEmpty());
}
どちらの場合でも nullEmptyAndBlankStrings(String) メソッドは6回実行されます。 null で1回、空値で1回、@ValueSource の提供する空白文字で4回です。
@EnumSource

@EnumSource を使うと列挙型の定数が使いやすくなります。

@ParameterizedTest
@EnumSource(ChronoUnit.class)
void testWithEnumSource(TemporalUnit unit) {
    assertNotNull(unit);
}

value 属性は任意です。 未指定なら先頭のメソッド引数の型を使用します。 先頭のメソッド引数の型が列挙型でなければテストは失敗します。 前のコード例では、先頭のメソッド引数の型 TemporalUnit は列挙型ではありません(ChronoUnit の実装するインターフェイスです)。 ですので、@EnumSourcevalue 属性に列挙型を指定しなければなりません。 引数の型を ChronoUnit へ変更すれば、value 属性の指定は不要になります。

@ParameterizedTest
@EnumSource
void testWithEnumSourceWithAutoDetection(ChronoUnit unit) {
    assertNotNull(unit);
}

names 属性には使用する定数名を指定できます。 未指定なら全ての定数を使用します。

@ParameterizedTest
@EnumSource(names = { "DAYS", "HOURS" })
void testWithEnumSourceInclude(ChronoUnit unit) {
    assertTrue(EnumSet.of(ChronoUnit.DAYS, ChronoUnit.HOURS).contains(unit));
}

mode 属性を指定するとテストメソッドに渡す定数を細かく制御できます。 次のコード例では指定した名前に一致する定数を除外したり、指定した正規表現がマッチする定数を選択したりします。

@ParameterizedTest
@EnumSource(mode = EXCLUDE, names = { "ERAS", "FOREVER" })
void testWithEnumSourceExclude(ChronoUnit unit) {
    assertFalse(EnumSet.of(ChronoUnit.ERAS, ChronoUnit.FOREVER).contains(unit));
}
@ParameterizedTest
@EnumSource(mode = MATCH_ALL, names = "^.*DAYS$")
void testWithEnumSourceRegex(ChronoUnit unit) {
    assertTrue(unit.name().endsWith("DAYS"));
}
@MethodSource

@MethodSource では、テストクラスやそれ以外のクラスに定義した、1つ以上の ファクトリ メソッドを指定できます。

テストクラスを @TestInstance(Lifecycle.PER_CLASS) で修飾している場合を除いて、テストクラス中のファクトリメソッドは static 宣言したものにしなければいけません。 一方、外部のクラスに定義したファクトリメソッドは必ず static 宣言したものにしなければいけません。 あと、ファクトリメソッドに指定するメソッドは引数を宣言できません。

ファクトリメソッドは 引数に渡す値ストリーム を生成しなければなりません。 ストリームのそれぞれの要素は @ParameterizedTest で修飾したメソッドを呼び出すたびに渡されます。 一般的には ArgumentsStream つまり Stream<Arguments> という形式で表現する場合が多いようです。 ただし、ファクトリメソッドの返り値型は他にもいろいろな型になります。 JUnit は安全に Stream へ変換できる型を「ストリーム」と呼びます(それぞれのプリミティブ型に対応する Stream や、オブジェクト型の配列、プリミティブ型の配列、CollectionIteratorIterable など)。 そして、Arguments のインスタンスとして渡せるものならなんでも「ストリームの要素」になります(オブジェクト型の配列や、単一のリテラル値など)。

テストメソッドの引数が1つだけなら、引数と同じ型の Stream を返すファクトリメソッドを使用できます。

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

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

@MethodSource で明示的にメソッド名を指定したくないときのために、JUnit Jupiter では @ParameterizedTest で修飾したメソッド名と同じ名前のメソッドを探索して、 ファクトリメソッド として使うようになっています。

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

static Stream<String> testWithDefaultLocalMethodSource() {
    return Stream.of("apple", "banana");
}

ファクトリメソッドの返り値型には、プリミティブ型に対応する Stream を使用できます。

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

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

パラメタライズテストに複数の引数を宣言した場合、ファクトリメソッドの返り値型は Arguments を要素型とするコレクション、ストリーム、配列にするか、オブジェクト型の配列にしなければなりません(対応している返り値型について詳しくは @MethodSource の Javadoc を参照してください)。 arguments(Object…​)Arguments インターフェイスに定義した static ファクトリメソッドです。 代わりに Arguments.of(Object…​) を使うこともできます。

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

static Stream<Arguments> stringIntAndListProvider() {
    return Stream.of(
        arguments("apple", 1, Arrays.asList("a", "b")),
        arguments("lemon", 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#tinyStrings")
    void testWithExternalMethodSource(String tinyString) {
        // test with tiny string
    }
}

class StringsProviders {

    static Stream<String> tinyStrings() {
        return Stream.of(".", "oo", "OOO");
    }
}
@CsvSource

@CsvSource を使うと、引数のリストをカンマ区切りで表現できます(例えば文字列リテラル)。

@ParameterizedTest
@CsvSource({
    "apple,         1",
    "banana,        2",
    "'lemon, lime', 0xF1",
    "strawberry,    700_000"
})
void testWithCsvSource(String fruit, int rank) {
    assertNotNull(fruit);
    assertNotEquals(0, rank);
}

既定の区切り文字はカンマ(,)ですが、delimiter 属性で別の文字を指定できます。 delimiterString 属性には区切り文字ではなく区切り文字列を指定できます。 両方の属性を同時に設定することはできません。

既定の囲み文字は一重引用符(')です。 この次の具体例にあるとおり、'lemon, lime' のように記述できます。 emptyValue 属性が未指定なら '' は空文字列になります。 囲み文字すら無い完全な 空値null になります。 nullValues 属性に指定した1つ以上の文字列のいずれかに対応する値は null になります(この次の具体例で NIL の例を参照してください)。 プリミティブ型のメソッド引数に null を渡すと ArgumentConversionException をスローします。

nullValues 属性にどんな値を設定しても、囲み文字で囲んでない 空値は常に null になります。

既定では、囲み文字があってもなくても、CSV の列の値の前後の空白は除去します。 ignoreLeadingAndTrailingWhitespace 属性に false を設定すると、空白を除去しなくなります(JUnit Jupiter 5.8 で導入された機能です)。

サンプルコード 引数に渡される値のリスト

@CsvSource({ "apple, banana" })

"apple", "banana"

@CsvSource({ "apple, 'lemon, lime'" })

"apple", "lemon, lime"

@CsvSource({ "apple, ''" })

"apple", ""

@CsvSource({ "apple, " })

"apple", null

@CsvSource(value = { "apple, banana, NIL" }, nullValues = "NIL")

"apple", "banana", null

@CsvSource(value = { " apple , banana" }, ignoreLeadingAndTrailingWhitespace = false)

" apple ", " banana"

@CsvFileSource

@CsvFileSource を使うとクラスパスやローカルファイルシステムに配置した CSV ファイルを使用できます。 CSV ファイルの各行ごとに、パラメタライズテストのテストメソッドを実行します。

既定の区切り文字はカンマ(,)ですが、delimiter 属性で別の文字を指定できます。 delimiterString 属性には区切り文字ではなく区切り文字列を指定できます。 両方の属性を同時に設定することはできません。

CSVファイル中のコメント行の扱い
\# で始まる行はコメントと見做して、すべて無視します。
@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSourceFromClasspath(String country, int reference) {
    assertNotNull(country);
    assertNotEquals(0, reference);
}

@ParameterizedTest
@CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSourceFromFile(String country, int reference) {
    assertNotNull(country);
    assertNotEquals(0, reference);
}
two-column.csv
Country, reference
Sweden, 1
Poland, 2
"United States of America", 3
France, 700_000

@CsvSource と違って、@CsvFileSource では囲み文字として二重引用符(")を使います。 前の例では "United States of America" のように使っています。 emptyValue 属性が未指定なら "" は空文字列になります。 囲み文字すら無い完全な 空値null になります。 nullValues 属性に指定した1つ以上の文字列のいずれかに対応する値は null になります。 プリミティブ型のメソッド引数に null を渡すと ArgumentConversionException をスローします。

nullValues 属性にどんな値を設定しても、囲み文字で囲んでない 空値は常に null になります。

既定では、囲み文字があってもなくても、CSV の列の値の前後の空白は除去します。 ignoreLeadingAndTrailingWhitespace 属性に false を設定すると、空白を除去しなくなります(JUnit Jupiter 5.8 で導入された機能です)。

@ArgumentsSource

@ArgumentsSource は自作の再利用可能な ArgumentsProvider を指定するために使います。 ArgumentsProvider の実装クラスはトップレベルクラスか static 宣言した内部クラスにしなければいけません。

@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("apple", "banana").map(Arguments::of);
    }
}

2.15.4. 引数の変換

ワイドニング変換

JUnit Jupiter はパラメタライズテストに渡される引数の プリミティブ型のワイドニング変換 に対応しています。 例えば、テストメソッドを @ValueSource(ints = { 1, 2, 3 }) で修飾した場合、メソッド引数の型には int, long, float, double が使用できます。

暗黙的な型変換

JUnit Jupiter は @CsvSource に必要な暗黙の型変換処理をいくつも提供しています。 メソッド引数の型によって、異なる変換処理を実行するということです。

例えば、パラメタライズテストのメソッド引数の型が TimeUnit で、ソースのデータ型が String なら、TimeUnit 列挙型の対応する定数へ自動的に変換します。

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

String からは、次のようなメソッド引数の型へ暗黙的に型変換します。

String リテラルで10進数記法、16進数記法、8進数記法の数値を指定した場合、byte, short, int, long の対応するプリミティブ型か、プリミティブ型に対応するラッパークラスへ変換します。
Target Type Example

boolean/Boolean

"true"true

byte/Byte

"15", "0xF", or "017"(byte) 15

char/Character

"o"'o'

short/Short

"15", "0xF", or "017"(short) 15

int/Integer

"15", "0xF", or "017"15

long/Long

"15", "0xF", or "017"15L

float/Float

"1.0"1.0f

double/Double

"1.0"1.0d

Enum の派生クラス

"SECONDS"TimeUnit.SECONDS

java.io.File

"/path/to/file"new File("/path/to/file")

java.lang.Class

"java.lang.Integer"java.lang.Integer.class (内部クラスを指定するときは $ を使う。例えば "java.lang.Thread$State"

java.lang.Class

"byte"byte.class (プリミティブ型に対応しています)

java.lang.Class

"char[]"char[].class (配列型に対応しています)

java.math.BigDecimal

"123.456e789"new BigDecimal("123.456e789")

java.math.BigInteger

"1234567890123456789"new BigInteger("1234567890123456789")

java.net.URI

"https://junit.org/"URI.create("https://junit.org/")

java.net.URL

"https://junit.org/"new URL("https://junit.org/")

java.nio.charset.Charset

"UTF-8"Charset.forName("UTF-8")

java.nio.file.Path

"/path/to/file"Paths.get("/path/to/file")

java.time.Duration

"PT3S"Duration.ofSeconds(3)

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.MonthDay

"--03-14"MonthDay.of(3, 14)

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.Period

"P2M6D"Period.of(0, 2, 6)

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.time.ZoneId

"Europe/Berlin"ZoneId.of("Europe/Berlin")

java.time.ZoneOffset

"+02:30"ZoneOffset.ofHoursMinutes(2, 30)

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

文字列からオブジェクトへ変換するときの代替処理

JUnit Jupiter は文字列からメソッド引数の型への型変換(前のセクションで表に列挙した)だけでなく、文字列からの変換処理が未定義の場合の代替処理として、自動的に単一の ファクトリメソッド あるいは、単一の ファクトリコンストラクタ を探索し、実行できます。

  • ファクトリメソッド: 可視性は package 以上。static 宣言している。引数は String 1つ。返り値は対象データ型のインスタンスである、対象データ型に定義したメソッド。メソッド名は自由です。

  • ファクトリコンストラクタ: 可視性は package 以上。引数は String 1 つ。対象データ型はトップレベルクラスか static 宣言したインナークラスにしなければいけません。

ファクトリメソッド を複数発見したときは何も見つからなかったものとします。ファクトリメソッドファクトリコンストラクタ が見つかったときはファクトリメソッドを優先して使用します。

次のコード例は、ファクトリメソッド Book.fromTitle(String)"42 Cats" から作成した Book のインスタンスをテストメソッドに渡しています。

@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 を指定することでデータ型を変換します。 ArgumentConverter の実装クラスはトップレベルクラスか static 宣言したインナークラスにしなければいけません。

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

    assertNotNull(ChronoUnit.valueOf(argument));
}
public class ToStringArgumentConverter extends SimpleArgumentConverter {

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

データ型を変換するだけなら、TypedArgumentConverter を継承すれば、定型的な型チェックを省略できます。

public class ToLengthArgumentConverter extends TypedArgumentConverter<String, Integer> {

    protected ToLengthArgumentConverter() {
        super(String.class, Integer.class);
    }

    @Override
    protected Integer convert(String source) {
        return (source != null ? source.length() : 0);
    }

}

引数の明示的な型変換を実装するのは、テストや拡張機能の作者の責任です。 それゆえに、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());
}

2.15.5. 引数の集約

既定では、パラメタライズテストに渡すそれぞれの 引数 は、1つ1つのメソッド引数に対応します。 つまり、複数の引数をかたまりとして提供するソースには、それだけ多くのメソッド引数が必要になるのです。

そういう場合はメソッド引数を複数宣言する代わりに ArgumentsAccessor を使用します。 この API を使用すると、複数の引数を単一のメソッド引数に渡すことができるようになります。 また、データ型の変換は 暗黙的な型変換 と同様に行われます。

@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 から引数へアクセスする代わりに、独自の再利用可能な アグリゲーター を使用できるようになっています。

独自のアグリゲーターをパラメタライズテストで使用するには、互換性のあるデータ型のメソッド引数を @AggregateWith で修飾し、ArgumentsAggregator インターフェイスの実装クラスを指定します。 テストメソッドを実行するとき、メソッド引数には集約した結果が渡されます。 ArgumentsAggregator の実装クラスはトップレベルクラスか static 宣言したインナークラスにしなければいけません。

@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 のような 合成アノテーション が欲しくなるでしょう。 次のコード例は PersonAggregator を使用する @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 {
}

2.15.6. 表示名の変更

パラメタライズテストの表示名には、呼び出し回数を表す数値と、全てのメソッド引数の文字列表現が含まれています。 ArgumentsAccessorArgumentAggregator 以外のメソッド引数の値の前には、バイトコードに含まれている引数名が指定されています(テストコードをコンパイルするときに -parameters フラグを指定した場合です)。

しかし、@ParameterizedTestname 属性に指定した文字列パターンで表示名は変更できます。

@DisplayName("Display name of container")
@ParameterizedTest(name = "{index} ==> the rank of ''{0}'' is {1}")
@CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 3" })
void testWithCustomDisplayNames(String fruit, int rank) {
}

前のコード例を ConsoleLauncher で実行すると、次のように出力されます。

Display name of container ✔
├─ 1 ==> the rank of 'apple' is 1 ✔
├─ 2 ==> the rank of 'banana' is 2 ✔
└─ 3 ==> the rank of 'lemon, lime' is 3 ✔

name 属性に指定できるのは MessageFormat が対応している文字列パターンです。 一重引用符(')を使いたいときは2つ続けて('')のようにしなければなりません。

表示名の文字列パターンが対応しているプレースホルダは次のとおりです。

プレースホルダ 内容

{displayName}

メソッド名

{index}

1から数え始める呼び出し回数

{arguments}

カンマ区切りにした引数の値

{argumentsWithNames}

カンマ区切りにした引数の名前と値

{0}, {1}, …​

位置に対応する引数の値

引数を表示名に含めるとき、最大文字列長を超過するなら文字列表現は短縮されます。最大文字列長は junit.jupiter.params.displayname.argument.maxlength で指定できます。初期値は 512 文字です。

@MethodSource@ArgumentSource を使うときは引数に名前を指定できます。 表示名の文字列パターンに引数の名前が含まれているときは、指定した名前が使われます。

@DisplayName("A parameterized test with named arguments")
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("namedArguments")
void testWithNamedArguments(File file) {
}

static Stream<Arguments> namedArguments() {
    return Stream.of(arguments(Named.of("An important file", new File("path1"))),
        arguments(Named.of("Another file", new File("path2"))));
}
A parameterized test with named arguments ✔
├─ 1: An important file ✔
└─ 2: Another file ✔

プロジェクトに含まれる全てのパラメタライズテストで使用する表示名の文字列パターンを指定するには、junit-platform.properties を次のように記述します。

junit.jupiter.params.displayname.default = {index}

パラメタライズテストの表示名を決定する規則は次のとおりです。

  1. @ParameterizedTestname 属性の値

  2. (存在するなら)junit-platform.properties で指定された junit.jupiter.params.displayname.default の値

  3. @ParameterizedTest に定義された定数 DEFAULT_DISPLAY_NAME の値

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

パラメタライズテストのそれぞれの呼び出しには、普通の @Test メソッドを呼び出すのと同じライフサイクルが伴います。 例えば、@BeforeEach は毎回呼び出されるということです。 実行時にテストを生成する と同様に、IDE からはそれぞれの呼び出し結果が1つのツリーにぶら下がるように見えることになります。 同じテストクラスで @ParameterizedTest と普通の @Test を混ぜて使用できます。

@ParameterizedTestParameterResolver 拡張を組み合わせて使用するとしても、メソッド引数の前のほうには @ArgumentSource で解決する引数を定義しなければなりません。 テストクラスにパラメタライズテストと普通のテストメソッドを定義している、かつ、普通のテストメソッドにはパラメタライズテストと異なる引数を定義しているとしたら、普通のテストメソッドを実行するとき、@BeforeEach などのライフサイクルメソッドやテストクラスのコンストラクタでは、@ArgumentSource による引数の解決は行われません。

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

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

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

2.16. テストのテンプレート化

@TestTemplate で修飾したメソッドは、普通のテストメソッドではなくテンプレートとして使われます。 登録したプロバイダーの返す呼び出しコンテキストの数と同じだけ呼び出す前提で設計します。 つまり、定義済みの TestTemplateInvocationContextProvider 拡張と組み合わせて使用しなければなりません。 それぞれの呼び出しは、普通の @Test メソッドと完全に同じライフサイクルメソッドと拡張機能に対応しています。 具体的な使い方は テストテンプレートに呼び出し元のコンテキストを与える を参照してください。

繰り返しテストパラメタライズテスト はテストのテンプレート化機能で実装されています。

2.17. 実行時にテストを生成する

JUnit Jupiter における基本的な @Test アノテーション(アノテーション を参照)は、JUnit 4 の @Test と非常によく似た機能を提供します。 あるメソッドがテストを実装したものであることを示しているのです。 これらのテストはコンパイル時に確定するため、ある意味静的なものであり、実行時に振る舞いを変更できません。 仮定(Assumption)は動的な振る舞いの基本的な形態ではありますが、その表現力は意図的に制限されています。

JUnit Jupiter では、これまでの標準的なテストとは全く異なる新しいテストプログラミングモデルとして 動的テスト を導入しました。 @TestFactory で修飾したファクトリメソッドで、実行時にテストメソッドを生成するのです。

普通のテストメソッドと違って、@TestFactory で修飾したメソッドはテストメソッドではなく、ファクトリメソッドなのです。 ファクトリメソッドで、動的テストを生成するのです。 具体的には、@TestFactory で修飾したファクトリメソッドの返り値は、単独の DynamicNode か、DynamicNode を要素とする Stream, Collection, Iterable, Iterator あるいは配列にします。 DynamicNode の派生クラス DynamicContainer および DynamicTest はインスタンス化可能です。 DynamicContainer表示名DynamicNode のリストを組み合わせたもので、任意の入れ子構造を作成できます。 DynamicTest は遅延評価(実行)するインスタンスで、実行時に、非決定論的なテストを表現できます。

@TestFactory メソッドの返り値が Stream の場合、Files.lines() のようにリソースを安全に使うため、stream.close() で閉じないといけいません。

@Test メソッドと同じく、@TestFactory メソッドの可視性は private にできません。 また、static 宣言することもできません。 さらに、ParameterResolvers でメソッド引数を解決できます。

DynamicTest は実行時に生成するテストケースで、表示名Executable を組み合わせたものです。 Executable@FunctionalInterface の実装クラスのことで、動的クラスにおいては ラムダ式メソッド参照 として記述できます。

動的テストのライフサイクル
動的テストのライフサイクルは普通のテストメソッドとは大きく異なります。特に違うのは、動的テストにはライフサイクルメソッドの呼び出しが伴わないことです。 つまり、@TestFactory メソッドを呼び出すときは @BeforeEach@AfterEach メソッド(および関連する拡張機能)も呼び出されるのですが、動的テスト自体を実行するときは呼び出されないのです。 言い換えると、動的テストの実装であるラムダ式からテストクラスのインスタンスフィールドへアクセスするとしても、同じ @TestFactory メソッドの生成したそれぞれの動的テストを呼び出している間に、フィールドを初期化するライフサイクルメソッドは呼び出されないのです。

JUnit Jupiter 5.8.0 の時点では、ファクトリメソッドでしか動的テストを生成できませんが、将来的には登録方式でも生成できるようになるでしょう。

2.17.1. 動的テストの具体例

次のコード例では DynamicTestsDemo クラスにより動的テストのいろいろな使い方を紹介します。

最初のメソッドは返り値の型が間違っているのですが、コンパイル時に検出できないので、実行時に JUnitException をスローします。

2つ目から続く6つのメソッドは、DynamicTest を要素とする Collection, Iterable, Iterator, 配列, Stream を返り値の型にしています。 特に動的な振る舞いを説明するものではありませんが、対応している返り値の型は分かると思います。 dynamicTestsFromStream()dynamicTestsFromIntStream() を見れば、複数の文字列や、数値の範囲に基づく動的なテストを簡単に生成できるのが分かります。

generateRandomNumberOfTests() は動的テストの本来の性質を活用しています。 乱数を生成する Iterator、表示名を生成するラムダ式、テストロジックを実装したラムダ式を DynamicTest.stream() へ渡しています。 どの辺が本来の性質なのかというと、実行するたびに結果が変化するところです。 動的テストの表現力を証明するためのコード例なので、実際に使用する場合は注意してください。

generateRandomNumberOfTests() と同じように柔軟性を活用しているのが dynamicTestsFromStreamFactoryMethod() です。 ファクトリメソッドの DynamicTest.stream()Stream を渡しているのが違うところです。

dynamicNodeSingleTest() が単一の DynamicTest を返すようになっているのはあくまでも説明用です。 dynamicNodeSingleContainer() では、DynamicContainer で動的テストの入れ子構造を作成しています。

import static example.util.StringUtils.isPalindrome;
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 static org.junit.jupiter.api.Named.named;

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 example.util.Calculator;

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

class DynamicTestsDemo {

    private final Calculator calculator = new Calculator();

    // 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(isPalindrome("madam"))),
            dynamicTest("2nd dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
        );
    }

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

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

    @TestFactory
    DynamicTest[] dynamicTestsFromArray() {
        return new DynamicTest[] {
            dynamicTest("7th dynamic test", () -> assertTrue(isPalindrome("madam"))),
            dynamicTest("8th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
        };
    }

    @TestFactory
    Stream<DynamicTest> dynamicTestsFromStream() {
        return Stream.of("racecar", "radar", "mom", "dad")
            .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))));
    }

    @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> generateRandomNumberOfTestsFromIterator() {

        // 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<DynamicTest> dynamicTestsFromStreamFactoryMethod() {
        // Stream of palindromes to check
        Stream<String> inputStream = Stream.of("racecar", "radar", "mom", "dad");

        // Generates display names like: racecar is a palindrome
        Function<String, String> displayNameGenerator = text -> text + " is a palindrome";

        // Executes tests based on the current input value.
        ThrowingConsumer<String> testExecutor = text -> assertTrue(isPalindrome(text));

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

    @TestFactory
    Stream<DynamicTest> dynamicTestsFromStreamFactoryMethodWithNames() {
        // Stream of palindromes to check
        Stream<Named<String>> inputStream = Stream.of(
                named("racecar is a palindrome", "racecar"),
                named("radar is also a palindrome", "radar"),
                named("mom also seems to be a palindrome", "mom"),
                named("dad is yet another palindrome", "dad")
            );

        // Returns a stream of dynamic tests.
        return DynamicTest.stream(inputStream,
            text -> assertTrue(isPalindrome(text)));
    }

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

    @TestFactory
    DynamicNode dynamicNodeSingleTest() {
        return dynamicTest("'pop' is a palindrome", () -> assertTrue(isPalindrome("pop")));
    }

    @TestFactory
    DynamicNode dynamicNodeSingleContainer() {
        return dynamicContainer("palindromes",
            Stream.of("racecar", "radar", "mom", "dad")
                .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))
        ));
    }

}

2.17.2. 動的テストのための URI テストソース

JUnit Platform の提供する TestSource は、テストやテストコンテナを表現するインターフェイスです。 テストメソッドの作成位置や取得元を IDE およびビルドツールに教えるために使用します。

動的テスト(あるいは動的テストコンテナ)の TestSource を作成するときは、 DynamicTest.dynamicTest(String, URI, Executable)DynamicTest.dynbamicContainer(String, URI, Stream) のようにファクトリメソッドへ java.net.URI を渡すようにします。 JUnit Jupiter は URI を解釈して次のいずれかの実装クラスを生成します。

ClasspathResourceSource

URI のプロトコルスキームが classpath のとき。例えば classpath:/test/foo.xml?line=20,column=2

DirectorySource

URI がファイルシステム上のディレクトリを指しているとき。

FileSource

URI がファイルシステム上のファイルを指しているとき。

MethodSource

URI のプロトコルスキームが method で、位置が完全修飾メソッド名(FQMN:Fully Qualified Method Name)のとき。例えば method:org.junit.Foo#bar(Java.lang.String, java.lang.String[]) FQMN の形式については DiscoverySelectors.selectMethod(String) の Javadoc を参照してください。

ClassSource

URI のプロトコルスキームが class で、位置が完全修飾クラス名(FQCN:Fully Qualified Class Name)のとき。例えば class:org.junit.Foo#line=42

UriSource

上記のどれにも該当しないとき。

2.18. タイムアウト

テストメソッド、テストファクトリメソッド、テストテンプレート、ライフサイクルメソッドのいずれかを @Timeout で修飾すると、指定した時間を過ぎても終了しなかったら失敗させることができるようになります。 時間単位の初期値は秒ですが、自由に変更できます。

次のコード例は @Timeout の使い方を説明しています。

class TimeoutDemo {

    @BeforeEach
    @Timeout(5)
    void setUp() {
        // fails if execution time exceeds 5 seconds
    }

    @Test
    @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
    void failsIfExecutionTimeExceeds100Milliseconds() {
        // fails if execution time exceeds 100 milliseconds
    }

}

assertTimeoutPreemptively() と違って、修飾したメソッドの実行時間は、テストの主スレッドが計測します。 メソッドの実行時間が指定した時間を超過したら、他のスレッドから主スレッドに対して割り込みを発生させます。 どうしてこういう仕組みにしているのかというと、Spring Framework のように、テストメソッドを実行しているスレッドの ThreadLocal でトランザクション状態を管理している場合があるからです。

トップレベルクラスを @Timeout で修飾すれば、(@Nested クラスで定義したメソッドも含めて)全てのテストメソッド、テストファクトリ、テストテンプレートに同じ時間制限を適用できます。 それぞれのメソッド(あるいは @Nested クラス)で時間制限を上書きするには @Timeout で修飾します。 クラスを修飾した場合、ライフサイクルメソッドに時間制限は適用されない点に注意してください。

テストファクトリを @Timeout で修飾しても、テストファクトリを実行する時間、つまり、動的テストを生成する時間だけがチェックされます。 それぞれの動的テストで実行時間を制限するには assertTimeout()assertTimeoutPreemptively() を使用してください。

@RepeatedTest@ParameterizedTest などのテストテンプレートについては、それぞれのテストメソッドについて @Timeout で指定した時間制限が適用されます。

次の 設定パラメータ を設定すると、テストメソッドやライフサイクルメソッドの種類に応じて、@Timeout を指定しなかったメソッドのタイムアウト時間を変更できます。

junit.jupiter.execution.timeout.default

全てのテストメソッドとライフサイクルメソッドのタイムアウト時間。

junit.jupiter.execution.timeout.testable.method.default

全てのテストメソッドのタイムアウト時間。

junit.jupiter.execution.timeout.test.method.default

@Test で修飾したメソッドのタイムアウト時間。

junit.jupiter.execution.timeout.testtemplate.method.default

@TestTemplate で修飾したメソッドのタイムアウト時間。

junit.jupiter.execution.timeout.testfactory.method.default

@TestFactory で修飾したメソッドのタイムアウト時間。

junit.jupiter.execution.timeout.lifecycle.method.default

全てのライフサイクルメソッドのタイムアウト時間。

junit.jupiter.execution.timeout.beforeall.method.default

@BeforeAll で修飾したメソッドのタイムアウト時間。

junit.jupiter.execution.timeout.beforeeach.method.default

@BeforeEach で修飾したメソッドのタイムアウト時間。

junit.jupiter.execution.timeout.aftereach.method.default

@AfterEach で修飾したメソッドのタイムアウト時間。

junit.jupiter.execution.timeout.afterall.method.default

@AfterAll で修飾したメソッドのタイムアウト時間。

より抽象度の低い設定項目は、抽象度の高い設定項目の値を上書きします。 具体的には、junit.jupiter.execution.timeout.test.method.defaultjunit.jupiter.execution.timeout.testable.method.default を上書きし、junit.jupiter.execution.timeout.testable.method.defaultjunit.jupiter.execution.timeout.default を上書きします。

設定パラメータに記述する値の形式は <number> [ns|μs|ms|s|m|h|d] です(大文字小文字は区別しません)。 数値と時間単位の間の空白は無視します。 時間単位を指定しなかった場合は秒として解釈します。

Table 1. 設定パラメータにおけるタイムアウト時間の設定例
設定値 アノテーションで同じ結果になる記述

42

@Timeout(42)

42 ns

@Timeout(value = 42, unit = NANOSECONDS)

42 μs

@Timeout(value = 42, unit = MICROSECONDS)

42 ms

@Timeout(value = 42, unit = MILLISECONDS)

42 s

@Timeout(value = 42, unit = SECONDS)

42 m

@Timeout(value = 42, unit = MINUTES)

42 h

@Timeout(value = 42, unit = HOURS)

42 d

@Timeout(value = 42, unit = DAYS)

2.18.1. ポーリングテストで @Timeout を使用する

非同期処理を実行するコードでは、結果に対するアサーションを実行する前に、定期的に結果を確認するようなテストを書く場合があります。 そういうロジックは CountDownLatch のような同期のための仕組みで実装できる場合もありますが、できない場合が問題です。 例えば、テスト対象のコードが外部のメッセージブローカーのチャンネルにメッセージを送信する場合、送信が成功(完了)するまでアサーションを実行できません。 非同期処理をテストするコードでは、テストスイート全体がハングアップしないようタイムアウト時間を設定しなければなりません。

非同期処理のテストで定期的に結果を確認するためにタイムアウト時間を設定すれば、テストが永遠に止まらないことを避けられます。 次のコード例のように、@Timeout で「指定した時間まで待ち続ける」ロジックを簡単に記述できます。

@Test
@Timeout(5) // Poll at most 5 seconds
void pollUntil() throws InterruptedException {
    while (asynchronousResultNotAvailable()) {
        Thread.sleep(250); // custom poll interval
    }
    // Obtain the asynchronous result and perform assertions
}
非同期処理のテストにおいて、定期的な確認時間を指定する以上の柔軟性が必要なときは、外部ライブラリの Awaitility を検討してください。

2.18.2. 全体で @Timeout を無効化する

テスト対象のソースコードをデバッガで調べていると、タイムアウト時間を固定値にしたテストが失敗になってしまう場合があります。

JUnit Jupiter の設定パラメータ junit.jupiter.execution.timeout.mode には3種類の値(enabled, disabled, disabled_on_debug)を指定できます。初期値は enabled です。 disabled_on_debug にすると、JVM をデバッグモードで実行しているかどうかを -agentlib:jdwp フラグの有無で判断します。

2.19. 並列実行

並列実行機能は実験的な機能です
JUnit チームがこの機能を改善して、正式機能へ 昇格 できるよう、試用してフィードバックを提供してください。

JUnit Jupiter はテストを1スレッドで逐次的に実行します。 5.3 から導入された並列実行機能は、テスト実行時間を短縮するための機能で、使用するには明示的な設定が必要でした。 設定パラメータの junit.jupiter.execution.parallel.enabledtrue 指定しなければなりません。 設定する場所は Junit-platform.properties です(他の設定項目については 設定パラメータの構成 を参照してください)。

この設定項目を有効化するのは、テストを並列実行するための最初の一歩にすぎません。 設定項目を有効化しただけではテストの実行方法は変わりません。 テストツリーを構成するどのノードを並行して実行するかは、実行モードによって異なります。 実行モードに指定できる値は次の2つです。

SAME_THREAD

強制的に親ノードと同じスレッドで実行します。 例えば、テストメソッドに指定した場合、同じテストクラスの全ての @BeforeAll@AfterAll は同じスレッドで実行します。

CONCURRENT

リソースロックにより同じスレッドでの実行を強制されない限り、並行して実行します。

基本的にテストツリーのノードの実行モードは SAME_THREAD になっています。 これは設定パラメータの junit.jupiter.execution.parallel.mode.default で変更できます。 実行モードは @Execution アノテーションでも変更できます。 このアノテーションで修飾した要素やその子要素は、それぞれのテストクラスの中で並列に実行するようになります。

設定例:全てのテストを並列に実行する
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent

一部の例外を除いて、既定の実行モードは全てのノードに適用されます。 例外は、インスタンスライフサイクル管理がクラス単位(per-class)になっているテストクラスと、@TestMethodOrderMethodOrderer.Random` 以外の {MethodOrder} を指定しているテストクラスです。 前者の場合、テストの作成者はテストクラスがスレッド安全であることを保証しなければなりません。 後者の場合、並行実行と実行順序の指定が矛盾してしまいます。 それゆえに、どちらの場合もテストクラスやテストメソッドが @Execution(CONCURRENT) で修飾されている場合だけ、並行して実行するようになっています。

宣言的な 同期化機構 が使われているとしても、テストツリーの全てのノードの実行モードが CONCURRENT になっているなら、設定パラメータ に応じて全てを並行して実行します。 標準出力や標準エラー出力をキャプチャーする は独立して有効化しなければなりません。

さらに、トップレベルクラスの既定の実行モードを junit.jupiter.execution.parallel.mode.classes.default で変更できます。 両方の設定項目を組み合わせれば、テストクラスは並列に実行するけど、テストメソッドは同じスレッドで実行させることができます。

設定例:トップレベルクラスは並列に実行し、テストメソッドは同じスレッドで実行する
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = same_thread
junit.jupiter.execution.parallel.mode.classes.default = concurrent

逆に、同じテストクラスのテストメソッドは並列に実行するけど、トップレベルクラスは逐次的に実行させることもできます。

設定例:トップレベルクラスは逐次的に実行し、テストメソッドは並列に実行する
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = same_thread

次の図は、設定パラメータ junit.jupiter.execution.parallel.mode.defaultjunit.jupiter.execution.parallel.mode.classes.default の値の全ての(4種類の)組み合わせについて、2つのトップレベルクラス AB それぞれに定義した2つのテストメソッドがどのように実行させるかを示しています。

writing tests execution mode
Default execution mode configuration combinations

設定パラメータ junit.jupiter.execution.parallel.mode.classes.default を指定しなかったときは、代わりに junit.jupiter.execution.parallel.mode.default の設定値を使用します。

2.19.1. 設定方法

並列度とスレッドプールサイズの最大値は ParallelExecutionConfigurationStrategy で設定します。 JUnit Platform は dynamicfixed という2種類の実装を提供しています。 自分で作成する場合は custom を指定します。

To select a strategy, set the junit.jupiter.execution.parallel.config.strategy configuration parameter to one of the following options. 設定パラメータ junit.jupiter.execution.parallel.config.strategy に指定できるのは次の値です。

dynamic

利用できるCPU数(CPUコア数)に、設定パラメータ junit.jupiter.execution.parallel.config.dynamic.factor の値(初期値は 1 )を乗じた値を並列度とします。

fixed

設定パラメータ junit.jupiter.execution.parallel.config.fixed.parallelism で並列度を指定します。

custom

ParallelExecutionConfigurationStrategy の実装クラス(の完全修飾クラス名)を設定パラメータの junit.jupiter.execution.parallel.config.custom.class に指定します。

未設定の場合、係数 1dynamic が指定されたものとします。 つまり、並列度はCPU数(CPUコア数)と等しくなります。

並列度は並行動作するスレッド数の最大値そのものではありません
並行して実行するテストの数(スレッド数)は、並列度の設定値を超過する場合があります(JUnit Jupiter は設定値を超過しないことを保証しません)。 例えば、次のセクションで説明している同期化機構 ForkJoinPool では、設定した並列度を満たすだけのテストを実行し続けるために、並列度を越える数のスレッドを生成する場合があります。 したがって、テストを実行するリソースをきちんと制御したければ、並列度を制御するロジックを自分で作成しなければなりません。

2.19.2. 同期化

JUnit Jupiter は、実行モードを制御する @Execution アノテーションに加えて、宣言的に同期させるための @ResourceLock アノテーションを提供しています。 テストクラスやテストメソッドなど、同期アクセスの必要な共有リソースを @ResourceLock で修飾することで、安全に実行できることを保証します。 共有リソースを区別する名前は文字列で指定します。 独自の文字列を指定するか、Resources の定義済み定数 SYSTEM_PROPERTIES, SYSTEM_OUT, SYSTEM_ERR, LOCALE, TIME_ZONE を使用できます。

次のコード例に登場するテストを @ResourceLock使わないで 並列に実行すると、実行結果が フレーキー(flaky) になってしまいます。 JVM の同じシステムプロパティの読み書きには潜在的に競合状態があるため、ある時は成功し、ある時は失敗するようになってしまうのです。

共有リソースにアクセスするテストクラスやテストメソッドは @ResourceLock で修飾します。 JUnit Jupiter はこのアノテーションを参照して、並列に実行するテストが衝突しないことを保証します。

テストを独立して実行する

ほとんどのテストクラスは同期化せずに実行できるけど、一部のテストクラスは独立して実行させたい場合、後者のテストクラスを @Isolated で修飾するといいでしょう。 そうすれば、他のクラスは同時に(並行して)実行しつつ、{Isonated} で修飾したテストクラスだけは逐次的に実行できます。

文字列で指定した名前だけでなく、アクセスモードを指定して共有リソースを識別できます。 共有リソースに読み取りアクセス(READ)をする2つのテストは並列に実行できるけど、読み取りアクセスと書き込みアクセスの両方(READ_WRITE)が必要な2つのテストは並列に実行できません。

@Execution(CONCURRENT)
class SharedResourcesDemo {

    private Properties backup;

    @BeforeEach
    void backup() {
        backup = new Properties();
        backup.putAll(System.getProperties());
    }

    @AfterEach
    void restore() {
        System.setProperties(backup);
    }

    @Test
    @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ)
    void customPropertyIsNotSetByDefault() {
        assertNull(System.getProperty("my.prop"));
    }

    @Test
    @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
    void canSetCustomPropertyToApple() {
        System.setProperty("my.prop", "apple");
        assertEquals("apple", System.getProperty("my.prop"));
    }

    @Test
    @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
    void canSetCustomPropertyToBanana() {
        System.setProperty("my.prop", "banana");
        assertEquals("banana", System.getProperty("my.prop"));
    }

}

2.20. 組み込みの拡張機能

JUnit チームとしては、再利用可能な拡張機能を別のライブラリで提供することを推奨しています。 ですが、JUnit Jupiter API のアーティファクトには、ユーザーが頻繁に使用するであろう便利な拡張機能を同梱しています(依存ライブラリを追加しなくても使用できます)。

2.20.1. 一時ディレクトリ

一時ディレクトリ @TempDir は実験的な機能です
JUnit チームがこの機能を改善して、正式機能へ 昇格 できるよう、試用してフィードバックを提供してください。

TempDirectory 拡張機能は、一時的にテストが使用するディレクトリの作成と後始末をするための拡張機能です。 テストメソッド単位や、テストクラス単位で使用できます。 始めから登録されている拡張機能です。 java.nio.file.Pathjava.io.File のフィールドを @TempDir で修飾して使用します。 また、テストメソッドやライフサイクルメソッドについて、java.nio.file.Pathjava.io.File の引数を @TempDir で修飾して使用します。

次のコード例では、テストメソッドの引数を @TempDir で修飾しています。 作成された一時ディレクトリにファイルを作成し、その内容を確認しているのが分かります。

一時ディレクトリを使用するテストメソッド
@Test
void writeItemsToFile(@TempDir Path tempDir) throws IOException {
    Path file = tempDir.resolve("test.txt");

    new ListWriter(file).write("a", "b", "c");

    assertEquals(singletonList("a,b,c"), Files.readAllLines(file));
}

複数の引数を修飾すれば、複数の一時ディレクトリを作成し、注入できます。

複数の一時ディレクトリを使用するテストメソッド
@Test
void copyFileFromSourceToTarget(@TempDir Path source, @TempDir Path target) throws IOException {
    Path sourceFile = source.resolve("test.txt");
    new ListWriter(sourceFile).write("a", "b", "c");

    Path targetFile = Files.copy(sourceFile, target.resolve("test.txt"));

    assertNotEquals(sourceFile, targetFile);
    assertEquals(singletonList("a,b,c"), Files.readAllLines(targetFile));
}
テストクラス全体あるいはテストメソッド(アノテーションで修飾する場所によって異なります)に対して単一の一時ディレクトリを提供する、という昔の振る舞いに戻すときは、設定パラメータ junit.jupiter.tempdir.scopeper_context を設定します。 この設定パラメータは将来のリリースで廃止予定です。

コンストラクタ引数では @TempDir を使用できません。 ライフサイクルメソッドと特定のテストメソッドで同じ一時ディレクトリを使用するときは、テストクラスのインスタンスフィールドを @TempDir で修飾してください。

次のコード例では static フィールドの一時ディレクトリを 共有 ディレクトリとして使うようになっています。 こうすると、テストクラスの全てのライフサイクルメソッドとテストメソッドは同じ sharedTempDir へアクセスできます。 インスタンスフィールドを使えば、それぞれテストメソッドが別のディレクトリを使うようにできるので、隔離性は高まります。

A test class that shares a temporary directory across test methods
class SharedTempDirectoryDemo {

    @TempDir
    static Path sharedTempDir;

    @Test
    void writeItemsToFile() throws IOException {
        Path file = sharedTempDir.resolve("test.txt");

        new ListWriter(file).write("a", "b", "c");

        assertEquals(singletonList("a,b,c"), Files.readAllLines(file));
    }

    @Test
    void anotherTestThatUsesTheSameTempDir() {
        // use sharedTempDir
    }

}

3. JUnit 4 から移行する

JUnit Jupiter のプログラミングモデルや拡張モデルは、直接的に JUnit 4 の RulesRunners へ対応していません。 しかし、JUnit Jupiter へ移行するために既存のソースコードやテスト用の拡張機能や、独自のテスト基盤を全て書き換えてくれるとは考えられません。

そこで、新しい JUnit Platform で JUnit 3 と JUnit 4 のテストコードを実行できる、JUnit Vintage テストエンジン という安全な移行パスを提供することにしました。 JUnit Jupiter 専用のクラスとアノテーションが所属するのは org.junit.jupiter パッケージなので、JUnit 4 が同じクラスパスに存在していても、衝突することはありません。 既存の JUnit 4 のテストを保守しながら、JUnit Jupiter のテストを安全に追加できるのです。 また、JUnit チームは JUnit 4.x リリースの保守と不具合対応を継続していくつもりなので、開発者の皆さまは自分たちの都合に合わせて、十分な時間をかけて JUnit Jupiter へ移行できます。

3.1. JUnit Platform で JUnit 4 のテストを実行する

実行時クラスパスに junit-vintage-engine を置いてください。 それだけで、JUnit Platform launcher は自動的に JUnit 3 と JUnit 4 のテストを発見できるようになります。

junit5-samples リポジトリを見て、Gradle や Maven の設定方法を確認してください。

3.1.1. カテゴリー(Categories)の対応

JUnit Vintage テストエンジン は、@Category で修飾したテストクラスやテストメソッドについて、指定したカテゴリの完全修飾クラス名を タグ として公開します。 例えば、テストメソッドを @Category(Example.class) で修飾しているなら、"com.acme.Example" というタグとして公開します。 JUnit 4 の Categories ランナーは、JUnit Vintage テストエンジンの公開したタグからテストを選択したり発見したりできます。 そして、このタグの情報はテストを実行する前に参照できるようになっています(テストを実行する を参照)。

3.2. 移行のポイント

JUnit 4 のテストを JUnit Jupiter へ移行するときは、次のような話題に注意してください。

  • アノテーションは org.junit.jupiter.api パッケージで探す

  • アサーションは org.junit.jupiter.api.Assertions クラスで探す

    • org.junit.Assert のアサーションや、外部ライブラリのアサーション(https://joel-costigliola.github.io/assertj/[AssertJ] Hamcrest Truth など)をそのまま利用できます

  • アサンプションは org.junit.jupiter.api.Assumptions クラスで探す

    • JUnit Jupiter 5.4 から JUnit 4 の org.junit.Assume クラスのアサンプションを使用できるようになりました。 特に、テストを失敗させる代わりに中断させるため、JUnit 4 の AssumptionViolatedException をスローします。

  • @Before@After の代わりに @BeforeEach@AfterEach を使用する

  • @BeforeClass@AfterClass の代わりに @BeforeAll@AfterAll を使用する

  • @Ignore の代わりに @Disabled を使用する。あるいは組み込みの 条件に基づくテスト実行 を使用する

  • @Category の代わりに @Tag を使用する

  • @RunWith の代わりに @ExtendWith を使用する

  • @Rule@ClassRule の代わりに @ExtendWith@RegisterExtension を使用する

3.3. JUnit 4 の Rule 対応(制限付き)

前に述べたとおり、JUnit Jupiter は直接的に JUnit 4 の Rule へ対応していません。 JUnit チームも、大企業を含む多数の組織が独自のルールを活用していることは理解しているつもりです。 そこで、そういう組織に段階的な移行パスを提供するため、JUnit 4 Rule の一部をそのまま JUnit Jupiter で再実装することにしました。 アダプターを使用する実装になっているので、JUnit Jupiter の拡張モデルにおける意味論的な互換性は限定的です。 例えば、テストの全体的な実行フローを完全に変更するのは不可能です。

JUnit Jupiter の junit-jupiter-migrationsupport モジュールが対応している Rule (と一部の派生クラス)は次のとおりです。

  • org.junit.rules.ExternalResource (including org.junit.rules.TemporaryFolder)

  • org.junit.rules.Verifier (including org.junit.rules.ErrorCollector)

  • org.junit.rules.ExpectedException

JUnit 4 ではメソッドだけでなくフィールドも Rule で修飾できるようになっていました。 クラスレベルの拡張機能を追加することで、そういう Rule の使い方をしている従来のコードベースを そのまま 、import 文すら変更せずに実行できるようになります。

テストクラスを @EnableRuleMigrationSupport で修飾すると、制限付きの Rule 対応を実現できます(クラス単位で切り替えます)。 このアノテーションは 合成アノテーション で、全ての移行機能(VerifierSupport, ExternalResourceSupport, ExpectedExceptionSupport)を有効化します。 代わりにテストクラスを @EnableJunit4MigrationSupport で修飾すると、Rule の移行機能を登録するだけでなく JUnit 4 の @Ignore アノテーションの移行機能も登録します(JUnit 4 の @Ignore 対応 を参照)。

とはいえ、これから JUnit 5 のために拡張機能を作成するなら、JUnit 4 のルールモデルではなく、JUnit Jupiter の拡張モデルを使用してください。

3.4. JUnit 4 の @Ignore 対応

JUnit Jupiter へのスムーズ移行パスを実現するため、junit-jupiter-migrationsupport モジュールは JUnit 4 の @Ignore アノテーションに対応する機能を提供しています。

JUnit Jupiter のテストで @Ignore を使うには、テスト依存ライブラリjunit-jupiter-migrationsupport モジュールを追加して、テストクラスを @ExtendWith(IgnoreCondition.class) で修飾するか、@EnableJUnit4MigrationSupport で修飾します(後者は JUnit 4 の Rule 対応(制限付き) と同時に IgnoreCondition を自動的に登録します)。 IgnoreCondition@Ignore で修飾したテストクラスやテストメソッドを無効化する ExecutionCondition の実装クラスです。

import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport;

// @ExtendWith(IgnoreCondition.class)
@EnableJUnit4MigrationSupport
class IgnoredTestsDemo {

    @Ignore
    @Test
    void testWillBeIgnored() {
    }

    @Test
    void testWillBeExecuted() {
    }
}

4. テストを実行する

4.1. IDE でテストを実行する

4.1.1. IntelliJ IDEA

IntelliJ IDEA は 2016.2 から JUnit Platform でテストを実行できるようになりました。 詳しくは IntelliJ IDEA ブログの投稿 を参照してください。 ただし、実際には IntelliJ IDEA 2017.3 より新しいバージョンを使うことを推奨します。 プロジェクトで使用している junit-platform-launcher, junit-jupiter-engine, junit-vintage-engine のバージョンに対応した jar ファイルを自動的にダウンロードして使用するようになっているからです。

2017.3 より前のバージョンの IntelliJ IDEA は特定バージョンの JUnit 5 が同梱しています。 ですから、それより新しいバージョンの JUnit Jupiter でテストを実行すると、異なるバージョンのライブラリが同時に存在することになり、衝突してしまうからです。 そういう場合は、次の手順に従って、IntelliJ IDEA の同梱する JUnit 5 より新しいバージョンを使うようにしてください。

IDE の同梱する JUnit 5 と異なるバージョン(例えば 5.9.0-SNAPSHOT)を使用するには、対応するバージョンの junit-platform-launcher, junit-jupiter-engine, junit-vintage-engine の jar ファイルをクラスパスに配置しなければなりません。

Gradle の依存ライブラリを追加する
testImplementation(platform("org.junit:junit-bom:5.9.0-SNAPSHOT"))
// Only needed to run tests in a version of IntelliJ IDEA that bundles older versions
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
Maven の依存ライブラリを追加する
<!-- ... -->
<dependencies>
    <!-- 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>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.vintage</groupId>
        <artifactId>junit-vintage-engine</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.junit</groupId>
            <artifactId>junit-bom</artifactId>
            <version>5.9.0-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

4.1.2. Eclipse

Eclipse IDE は Eclipse Oxygen.1a (4.7.1a) から JUnit Platform でテストを実行できるようになりました。

詳しい使い方は公式ドキュメントの Eclipse Project Oxygen.1a (4.7.1a) - New and Noteworthy Eclipse support for JUnit 5 を参照してください。

4.1.3. NetBeans

NetBeans は Apache NetBeans 10.0 release から JUnit Platform で JUnit Jupiter のテストを実行できるようになりました。

詳しい使い方は公式ドキュメントの https://netbeans.apache.org/download/nb100/index.html#junit_5[Apache NetBeans 10.0 release notes] _JUnit 5 を参照してください。

4.1.4. Visual Studio Code

Visual Studio Code では、 Java Test Runner 拡張 を使うと JUnit Platform で JUnit Jupiter のテストを実行できます。 Java Test Runner 拡張は Java Extension Pack の一部として一緒にインストールできます。

詳しい使い方は公式ドキュメントの https://code.visualstudio.com/docs/languages/java#testing[Java in Visual Studio Code] _Testing を参照してください。

4.1.5. 他の IDE

前述した以外のエディターや IDE を使用する場合のため、JUnit チームは JUnit 5 の使用を支援する2種類の方法を提供しています。 1つは コンソールランチャー をコマンドラインで実行する方法です。 もう1つは JUnit 4 based Runner を実行する方法で、こちらはエディタや IDE が JUnit 4 に対応している場合に使用できる方法です。

4.2. ビルドツールでテストを実行する

4.2.1. Gradle

JUnit Platform Gradle プラグインの開発は修了しました

JUnit チームの開発していた junit-platform-gradle-plugin は JUnit Platform 1.2 で廃止予定になり、1.3 の開発は中止しました。 代わりに Gradle の標準 test タスクを使用してください。

Gradle は 4.6 から JUnit Platform で 直接的にテストを実行できる ようになりました。 この機能を使用するには build.gradletest タスクで useJUnitPlatform() を呼び出すようにします。

test {
    useJUnitPlatform()
}

タグタグ記法 で実行するテストを選択したり、テスト実行エンジンを指定できます。

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

使用可能な全ての設定項目については 公式ドキュメント を参照してください。

設定パラメータの構成

現在の Gradle の標準 test タスクには、JUnit Platform がテストを発見したり実行したりするための 設定パラメータ を指定する DSL が存在しません。 代わりにシステムプロパティや junit-platform.properties ファイルで指定します。

test {
    // ...
    systemProperty("junit.jupiter.conditions.deactivate", "*")
    systemProperty("junit.jupiter.extensions.autodetection.enabled", true)
    systemProperty("junit.jupiter.testinstance.lifecycle.default", "per_class")
    // ...
}
テスト実行エンジンの構成

1度に複数のテストを実行するには、クラスパスに TestEngine の実装クラスが存在しなければなりません。

JUnit Jupiter で作成したテストを実行するには、JUnit Jupiter の集約アーティファクトを testImplementation 依存関係で指定しなければなりません。

dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter:5.9.0-SNAPSHOT")
}

JUnit 4 で作成したテストを JUnit Platform で実行するには、JUnit 4 のアーティファクトを testImplementation 依存関係に指定するだけでなく、JUnit Vintage のアーティファクトを testRuntimeOnly 依存関係に指定しなければなりません。

dependencies {
    testImplementation("junit:junit:4.13.2")
    testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.9.0-SNAPSHOT")
}
ログ出力の設定(任意)

JUnit は警告情報やデバッグ情報を出力するため、Java の標準ログ API (java.util.logging パッケージ、JUL と呼ばれる)を使用します。 LogManager の設定方法について詳しくは 公式ドキュメント を参照してください。

JUL のログ出力は Log4jLogback のようなロギングフレームワークへ転送できます。 使用するロギングフレームワークが LogManager の実装クラスを提供している場合、システムプロパティの java.util.logging.manager完全修飾クラス名 を指定してください。 Log4j 2.x を使用する場合は次のように記述します(詳しくは Log4j JDK Logging Adapter を参照)。

test {
    systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager")
}

ロギングフレームワークとして Logback を使用するなら、runtime 依存関係に JUL to SLF4J Bridge を追加するといいでしょう。

4.2.2. Maven

JUnit Platform Maven Surefire Provider プラグインの開発は修了しました

JUnit チームの開発していた junit-platform-surefire-provider は JUnit Platform 1.3 で廃止予定になり、1.4 の開発は中止しました。 代わりに Maven Surefire で直接的に実行するようにしてください。

Maven Surefire および Maven Failsafe では 2.22.0 から JUnit Platform で 直接的にテストを実行できる ようになりました。 junit5-jupiter-starter-maven プロジェクトの pom.xml ファイルを見ると、Maven Surefire プラグインをどのように設定するのか分かります。

テスト実行エンジンの構成

Maven Surefire や Maven Failsafe で1度に複数のテストを実行するには、クラスパスに TestEngine の実装クラスが存在しなければなりません。

JUnit Jupiter で作成したテストを実行するには、JUnit Jupiter API と JUnit Jupiter TestEngine (の実装クラス)の依存ライブラリを test スコープで追加しなければなりません。

<!-- ... -->
<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.9.0-SNAPSHOT</version>
        <scope>test</scope>
    </dependency>
    <!-- ... -->
</dependencies>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
        <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
    </plugins>
</build>
<!-- ... -->

JUnit 4 で作成したテストを JUnit Jupiter で作成したテストと一緒に実行するには、JUnit 4 と JUnit Vintage TestEngine (の実装クラス)の依存ライブラリを test スコープで追加しなければなりません。

<!-- ... -->
<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.vintage</groupId>
        <artifactId>junit-vintage-engine</artifactId>
        <version>5.9.0-SNAPSHOT</version>
        <scope>test</scope>
    </dependency>
    <!-- ... -->
</dependencies>
<!-- ... -->
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
        <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
    </plugins>
</build>
<!-- ... -->
名前でテストクラスを選択する

Maven Surefire プラグインは、完全修飾クラス名が次のようなパス文字列パターンに一致するテストクラスを探索します。

  • **/Test*.java

  • **/*Test.java

  • **/*Tests.java

  • **/*TestCase.java

さらに、初期設定では全ての入れ子クラス(static メンバークラスも含めて)を無視します。

しかし、この振る舞いは明示的に pom.xmlinclude ルールと exclude ルールを追加することで変更できます。 例えば、static メンバークラスを無視する振る舞いを止めさせるには次のように exclude ルールを記述します。

Maven Surefire の exclude ルールを上書きする
<!-- ... -->
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.2</version>
            <configuration>
                <excludes>
                    <exclude/>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>
<!-- ... -->

詳しくは公式ドキュメントの Inclusions and Exclusions of Tests を参照してください。

タグでテストクラスを選択する

タグタグ記法 で実行するテストを選択できます。

  • タグタグ記法 を含むテストクラスを選択するには groups を使用します

  • タグタグ記法 を含むテストクラスを除外するには excludedGroups を使用します

<!-- ... -->
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.2</version>
            <configuration>
                <groups>acceptance | !feature-a</groups>
                <excludedGroups>integration, regression</excludedGroups>
            </configuration>
        </plugin>
    </plugins>
</build>
<!-- ... -->
設定パラメータの構成

JUnit Platform がテストを発見したり実行したりするための 設定パラメータconfigurationParameters で指定します。 configurationProperties には、 Java の Properties 記法でキーと値の対を記述します。 junit-platform.properties ファイルでも指定できます。

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

4.2.3. Ant

Starting with version 1.10.3 of Ant, a new junitlauncher task has been introduced to provide native support for launching tests on the JUnit Platform. The junitlauncher task is solely responsible for launching the JUnit Platform and passing it the selected collection of tests. The JUnit Platform then delegates to registered test engines to discover and execute the tests.

The junitlauncher task attempts to align as close as possible with native Ant constructs such as resource collections for allowing users to select the tests that they want executed by test engines. This gives the task a consistent and natural feel when compared to many other core Ant tasks.

Starting with version 1.10.6 of Ant, the junitlauncher task supports forking the tests in a separate JVM.

The build.xml file in the junit5-jupiter-starter-ant project demonstrates how to use the task and can serve as a starting point.

Basic Usage

The following example demonstrates how to configure the junitlauncher task to select a single test class (i.e., 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>

The test element allows you to specify a single test class that you want to be selected and executed. The classpath element allows you to specify the classpath to be used to launch the JUnit Platform. This classpath will also be used to locate test classes that are part of the execution.

The following example demonstrates how to configure the junitlauncher task to select test classes from multiple locations.

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

In the above example, the testclasses element allows you to select multiple test classes that reside in different locations.

For further details on usage and configuration options please refer to the official Ant documentation for the junitlauncher task.

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

ConsoleLauncher はコマンドラインから JUnit Platform を実行するための Java アプリケーションです。 JUnit Jupiter のテストを実行した結果をコンソールに出力できるし、JUnit Vintage のテスト実行エンジンも使用できます。

依存ライブラリを一緒にした実行可能 jar ファイル junit-platform-console-standalone-1.9.0-SNAPSHOT.jarMaven Central で公開されています。 JUnit Platform リポジトリの junit-platform-console-standalone ディレクトリを参照してください。 jar ファイルは次のように実行します。

java -jar junit-platform-console-standalone-1.9.0-SNAPSHOT.jar <コンソールランチャーの引数(フラグ)>

実行結果は次のようになるでしょう。

├─ 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          ]
終了コード
テストコンテナやテストが1つでも失敗すると ConsoleLauncher はステータスコード 1 で終了します。 コマンドラインオプションで --fail-if-no-tests フラグを指定した場合、1つもテストが見つからないときは、ステータスコード 2 で終了します。 それ以外の場合、ステータスコード 0 で終了します。

4.3.1. コンソールランチャーの引数(フラグ)

Usage: ConsoleLauncher [-h] [--disable-ansi-colors] [--disable-banner]
                       [--fail-if-no-tests] [--scan-modules] [--scan-classpath[=PATH[;|:
                       PATH...]]]... [--details=MODE] [--details-theme=THEME]
                       [--reports-dir=DIR] [-c=CLASS]... [--config=KEY=VALUE]... [-cp=PATH
                       [;|:PATH...]]... [-d=DIR]... [-e=ID]... [-E=ID]...
                       [--exclude-package=PKG]... [-f=FILE]... [--include-package=PKG]...
                       [-m=NAME]... [-n=PATTERN]... [-N=PATTERN]... [-o=NAME]...
                       [-p=PKG]... [-r=RESOURCE]... [-t=TAG]... [-T=TAG]... [-u=URI]...
Launches the JUnit Platform from the console.
  -h, --help                 Display help information.
      --disable-ansi-colors  Disable ANSI colors in output (not supported by all
                               terminals).
      --disable-banner       Disable print out of the welcome message.
      --details=MODE         Select an output details mode for when tests are executed.
                               Use one of: none, summary, flat, tree, verbose. If 'none'
                               is selected, then only the summary and test failures are
                               shown. Default: tree.
      --details-theme=THEME  Select an output details tree theme for when tests are
                               executed. Use one of: ascii, unicode. Default: unicode.
      -cp, --classpath, --class-path=PATH[;|:PATH...]
                             Provide additional classpath entries -- for example, for
                               adding engines and their dependencies. This option can be
                               repeated.
      --fail-if-no-tests     Fail and return exit status code 2 if no tests are found.
      --reports-dir=DIR      Enable report output into a specified local directory (will
                               be created if it does not exist).
      --scan-modules         EXPERIMENTAL: Scan all resolved modules for test discovery.
  -o, --select-module=NAME   EXPERIMENTAL: Select single module for test discovery. This
                               option can be repeated.
      --scan-classpath, --scan-class-path[=PATH[;|:PATH...]]
                             Scan all directories on the classpath or explicit classpath
                               roots. Without arguments, only directories on the system
                               classpath as well as additional classpath entries supplied
                               via -cp (directories and JAR files) are scanned. Explicit
                               classpath roots that are not on the classpath will be
                               silently ignored. This option can be repeated.
  -u, --select-uri=URI       Select a URI for test discovery. This option can be repeated.
  -f, --select-file=FILE     Select a file for test discovery. This option can be
                               repeated.
  -d, --select-directory=DIR Select a directory for test discovery. This option can be
                               repeated.
  -p, --select-package=PKG   Select a package for test discovery. This option can be
                               repeated.
  -c, --select-class=CLASS   Select a class for test discovery. This option can be
                               repeated.
  -m, --select-method=NAME   Select a method for test discovery. This option can be
                               repeated.
  -r, --select-resource=RESOURCE
                             Select a classpath resource for test discovery. This option
                               can be repeated.
  -n, --include-classname=PATTERN
                             Provide a regular expression to include only classes whose
                               fully qualified names match. To avoid loading classes
                               unnecessarily, the default pattern only includes class
                               names that begin with "Test" or end with "Test" or
                               "Tests". When this option is repeated, all patterns will
                               be combined using OR semantics. Default: [^(Test.*|.+[.$]
                               Test.*|.*Tests?)$]
  -N, --exclude-classname=PATTERN
                             Provide a regular expression to exclude those classes whose
                               fully qualified names match. When this option is repeated,
                               all patterns will be combined using OR semantics.
      --include-package=PKG  Provide a package to be included in the test run. This
                               option can be repeated.
      --exclude-package=PKG  Provide a package to be excluded from the test run. This
                               option can be repeated.
  -t, --include-tag=TAG      Provide a tag or tag expression to include only tests whose
                               tags match. When this option is repeated, all patterns
                               will be combined using OR semantics.
  -T, --exclude-tag=TAG      Provide a tag or tag expression to exclude those tests whose
                               tags match. When this option is repeated, all patterns
                               will be combined using OR semantics.
  -e, --include-engine=ID    Provide the ID of an engine to be included in the test run.
                               This option can be repeated.
  -E, --exclude-engine=ID    Provide the ID of an engine to be excluded from the test
                               run. This option can be repeated.
      --config=KEY=VALUE     Set a configuration parameter for test discovery and
                               execution. This option can be repeated.

4.3.2. コンソールランチャーの引数(@-files)

OSによっては、実行するコマンドライン文字列の長さが上限値に達してしまう場合があります。

JUnit Platform 1.3 から ConsoleLauncher引数ファイル を渡せるようになりました(@-files と呼ばれています)。 引数ファイルはコマンドライン引数を羅列したテキストファイルです。 この機能は picocli のコマンドライン文字列パーサーで実装されており、コマンドライン引数に @ で始まる項目があれば、対応するファイルの内容をコマンドライン引数に追加するようになっています。

引数ファイルでは、それぞれの引数を空白文字や改行文字で分割します。 引数自体に空白文字を含めるには、その引数全体を一重引用符や二重引用符で囲まなければなりません。 例えば "-f=My Files/Stuff.java" のように記述します。

指定した引数ファイルが存在しないか、読み取れない場合、ただの1引数として扱います。 おそらく「使用できない引数」という警告メッセージが出力されるでしょう。 コマンドを実行するとき、システムプロパティ picocli.traceDEBUG を指定すると問題の解決に役立つ情報が出力されるでしょう。

複数の引数ファイルを使用するときはコマンドライン引数に @-files を複数指定します。 ファイル名を完全パスで指定した場合以外は、実行時ディレクトリからの相対パスとして解釈します。

@ から始まるフラグ名を使いたいときは、@@ のように2つ続けて記述します。 例えば @@somearg@somearg として扱われるようになります(引数ファイルとして展開しません)。

4.4. JUnit 4 で JUnit Platform を実行する

JUnitPlatform ランナーは廃止予定です

JUnit チームが JUnitPlatform ランナーを開発したのは、JUnit 4 で作成したテスト(テストスイート)を実行するための暫定措置です。

ここ数年の間に、さまざまなビルドツールや IDE は JUnit Platform を直接的に実行できるようになりました。

また、junit-platform-suite-engine モジュールで @Suite が使えるようになったため、JUnitPlatform ランナーは不要になりました。 詳しくは JUnit Platform テストスイートエンジン を参照してください。

以上の理由により、JUnit Platform 1.8 から JUnitPlatform ランナーは廃止予定になりました。 おそらく JUnit Platform 2.0 で削除するでしょう。

JUnitPlatform ランナーを使用している場合は @Suite に対応した junit-platform-suite-engine モジュールへ移行してください。

JUnitPlatform ランナーは、JUnit Platform の提供するプログラミングモデルで作成したテストを、JUnit 4 の環境で実行するテストランナーです。

テストクラスを @RunWith(JUnitPlatform.class) で修飾すれば、JUnit Platform には対応していないけど JUnit 4 に対応した IDE やビルドツールからテストを実行できるようになります。

JUnit Platform には JUnit 4 にない機能が存在します。 したがって JUnitPlatform ランナーは JUnit Platform の提供する機能の一部だけしか対応していません。 特にテスト結果のレポート機能には違いがあります(表示名と部品名 を参照)。

4.4.1. 準備

JUnitPlatform ランナーを使用するには以降で説明するアーティファクトや依存ライブラリをクラスパスに配置しなければなりません。 具体的なグループID、アーティファクトID、バージョンについては 依存ライブラリに関するメタデータ を参照してください。

明示的な依存関係
  • junit-platform-runner test スコープ。JUnitPlatform ランナーそのものです

  • junit-4.13.2.jar test スコープ。JUnit 4 です

  • junit-jupiter-api test スコープ。JUnit Jupiter でテストを作成するための API です

  • junit-jupiter-engine testRuntime スコープ。JUnit Jupiter のための TestEngine 実装です

推移的依存関係
  • junit-platform-suite-api test スコープ。

  • junit-platform-suite-commons test スコープ。

  • junit-platform-launcher test スコープ。

  • junit-platform-engine test スコープ。

  • junit-platform-commons test スコープ。

  • opentest4j test スコープ。

4.4.2. 表示名と部品名

@RunWith(JUnitPlatform.class) で修飾したテストクラスを実行するときの 表示名 を変更するには、テストクラスを @SuiteDisplayName で修飾して、設定します。

初期設定では、テストアーティファクトの 表示名 を使うようになっています。 しかし、Gradle や Maven 等のビルドツールで JUnitPlatform ランナーを実行して、テスト結果のレポートを作成するときは、テストアーティファクトの 部品名 が必要になります。 例えば、テストクラスの省略形(表示名)や特殊文字を含む表示名ではなく、完全修飾クラス名が必要になる、といった具合です。 テスト結果のレポートを作成するときに部品名を使用できるようにするには、テストクラスを @UseTechnicalNames で修飾してください。

テストクラスを @UseTechnicalNames で修飾すると、@SuiteDisplayName で指定した表示名を上書きします。

4.4.3. 単独のテストクラス

テストクラスを @RunWith(JUnitPlatform.class) で修飾するのは JUnitPlatform ランナーを使用する方法の1つです。 次のコード例ではテストメソッドを JUnit Jupiter の @Test で修飾しているのがポイントです( org.junit.Test ではなく org.junit.jupiter.api.Test を使用しています)。 さらに、テストクラスの可視性が public になっているのもポイントです。 そうしないと JUnit 4 のテストクラスとして認識しない IDE やビルドツールがあるからです。

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

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

@RunWith(org.junit.platform.runner.JUnitPlatform.class)
public class JUnitPlatformClassDemo {

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

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

}

4.4.4. テストスイート

次のコード例のように、複数のテストクラスをまとめたテストスイートがあるとします。

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

@RunWith(org.junit.platform.runner.JUnitPlatform.class)
@SuiteDisplayName("JUnit Platform Suite Demo")
@SelectPackages("example")
public class JUnitPlatformSuiteDemo {
}

JUnitPlatformSuiteDemo クラスは example パッケージとそのサブパッケージに配置した全てのテストクラスを発見し、実行します。 初期設定では Test で始まるか、TestTests で終わる名前のクラスが対象になります。

さらに細かい設定
@SelectPackages 以外にもテストを発見したり選択したりするための設定項目があります。 詳しくは org.junit.platform.suite.api の Javadoc を参照してください。
@RunWith(JUnitPlatform.class) で修飾したテストスイートクラスやテストクラスは JUnit Platform から直接的に実行 できません (IDE のドキュメントに記載されている "JUnit 5" テストとしても実行できません)。 JUnit 4 でしか実行できません。

4.5. 設定パラメータの構成

テストクラスの選択や、テスト実行エンジンの指定、テストクラスを探索するパッケージの指定といった基盤機能の説明に加えて、特定のテスト実行エンジンやリスナー、拡張機能に固有の設定パラメータについても理解しておいたほうがいいでしょう。 例えば、JUnit Jupiter テスト実行エンジンには次のようなユースケースを想定した設定パラメータが用意されています。

設定パラメータ は文字列ベースのキーと値の対の集合です。 JUnit Platform を実行するときは次のように注入します。

  1. Launcher API のパラメータを構築する LauncherDiscoveryRequestBuilderconfigurationParameter() メソッドや configurationParameters() メソッドを使用します。 JUnit Platform を実行するツールによって渡し方は異なります。

  2. JVM のシステムプロパティを使用します。

  3. JUnit Platform の独自設定ファイルを使用します。Java の Properties ファイルとして作成した junit-platform.properties をクラスパスの最上位に配置します。

設定パラメータを読み取る順序は固定されています。 つまり、Launcher に直接指定したパラメータは、システムプロパティや設定ファイルなど他の方法で指定したパラメータを上書きするのです。 同様に、システムプロパティで指定したパラメータは、設定ファイルで指定したパラメータを上書きします。

4.5.1. パターンマッチ記法

このセクションでは、次のような機能のために 設定パラメータ へ記述するパターンマッチの記法を説明します。

設定値の文字列パターンがアスタリスク * 1つだけなら、全ての候補クラスにマッチします。 そうでなければ、設定値をカンマ区切り文字列(それぞれの文字列パターンは完全修飾クラス名(FQCN))と見做して、それぞれの文字列パターンと候補クラスが一致するか評価します。 文字列パターン中のドット(.)は、FQCN 中のドット(.)か、$マーク($)とマッチします。 文字列パターン中のアスタリスク(*)は、FQCN 中の連続する任意の1文字以上の文字列とマッチします。 それ以外の文字は、FQCN 中の同じ文字とマッチします。

具体例です。

  • *: 全ての候補クラスとマッチします

  • org.junit.*: org.junit パッケージとそのサブパッケージに配置した全ての候補クラスとマッチします any of its subpackages.

  • {asterrisk}.MyCustomImpl: 単純クラス名が MyCustomImpl になる全ての候補クラスとマッチします

  • {asterrisk}System{asterrisk}: FQCN に System を含む全ての候補クラスとマッチします

  • {asterrisk}System{asterrisk}, {asterrisk}Unit{asterrisk}: FQCN に System あるいは Unit を含む全ての候補クラスとマッチします

  • org.example.MyCustomImpl: FQCN が完全に org.example.MyCustomImpl と一致する候補クラスとマッチします

  • org.example.MyCustomImpl, org.example.TheirCustomImpl: FQCN が完全に org.example.MyCustomImpl と一致する、あるいは org.example.TheirCustomImpl と一致する候補クラスとマッチします

4.6. タグ

タグはテストクラスに印を付けたり、テストクラスを選択するための機能です。 テストコンテナやテストクラスにタグを追加するためのプログラミングモデルは、テストフレームワークが提供しています。 例えば、JUnit Jupiter でテストを作成するなら @Tag アノテーション(タグ付けと選択 を参照)を使用し、JUnit 4 で作成したテストなら Vintage テスト実行エンジンが @Category アノテーションをタグに写像します(カテゴリー(Categories)の対応 を参照)。 他のテストフレームワークも、ユーザーがタグを定義するために使用できる独自のアノテーションを提供しているでしょう。

4.6.1. タグの構文規則

タグを指定する方法がなんであれ、JUnit Platform は次の規則で解釈します。

  • null空白文字 は使用できません

  • トリムしたタグ値には、空白文字を使用できません

  • トリムしたタグ値には、ISO 制御文字を使用できません

  • トリムしたタグ値には、次のいずれかの文字(予約文字)を使用できません

    • ,: カンマ

    • (: 開き括弧

    • ): 閉じ括弧

    • &: アンパサンド

    • |: 縦線

    • !: 感嘆符

In the above context, "trimmed" means that leading and trailing whitespace characters have been removed. NOTE: 「トリムした」というのは、文字列の先頭や末尾から連続する1文字以上の空白文字を削除するということです。

4.6.2. タグ記法

タグ記法は !&| を演算子として使用するブール式です。 () で演算子の優先度を変更できるようになっています。

特殊記法として any()none() を使用できます。 前者はあらゆるタグと 一致する ことを評価し、後者はあらゆるタグと 一致しない ことを評価します。 これらの特殊記法は通常のタグ記法と組み合わせて使用できます。

Table 2. 演算子の優先度(降順:上のほうが優先)
演算子 意味 結合の方向

!

否定

&

かつ

|

あるいは

テストを複数の軸でタグ付けしているときは、タグ記法を使うと実行するテストを選択するのに役立ちます。 タグでテスト種類(microintegrationend-to-end など)や機能(productcatalogshipping など)を説明しているときは、次のように使用できます。

タグ記法

選択されたテストクラス

product

product を指定したテストクラス

catalog | shipping

catalog あるいは shipping を指定したテストクラス

catalog & shipping

catalogshipping を指定したテストクラス

product & !end-to-end

product を指定したテストクラスの中で end-to-end を指定していないもの

(micro | integration) & (product | shipping)

micro あるいは integration を指定したテストクラスの中で product あるいは shipping を指定しているもの

4.7. 標準出力や標準エラー出力をキャプチャーする

JUnit Platform は 1.3 から System.outSystem.err に出力した内容をキャプチャーする機能をオプトイン方式で提供しています。 この機能を有効化するには、システムプロパティや設定ファイル(設定パラメータの構成 を参照)で junit.platform.output.capture.stdout あるいは junit.platform.output.capture.stderrtrue を設定します。 また、実行時の出力バッファサイズをテストコンテナやテストクラスごとに変更するときは junit.platform.output.capture.maxBuffer を設定します。

この機能を有効化すると、JUnit Platform は標準出力(標準エラー出力)をキャプチャーします。 キャプチャーした内容は、登録済みの TestExecutionListener へ発行するレポート情報のキー stdoutstderr)で参照できます。 リスナーインスタンスは、テストコンテナやテストクラスの実行が完了する前に、レポート情報を参照できます。

なお、テストコンテナやテストクラスを実行したスレッドが標準出力(標準エラー出力)に出力した内容しかキャプチャーできません。 他のスレッドが出力した内容は無視されてしまいますし、特に 並列実行 させている場合は、どのテストコンテナやテストクラスが出力した内容なのか、後から区別できません。

4.8. リスナーを使用する

JUnit Platform の提供するリスナーAPIを使用すると、TestPlan がテストを発見したり実行したりする途中のさまざまなポイントで発火するイベントに、JUnit やサードパーティライブラリだけでなく、ユーザーの作成したコードが反応できるようになります。

基本的に LauncherDiscoveryListener API を使用するのはビルドツールや IDE が独自の機能を提供するためです。 テスト実行時に自動的に登録する場合が多いでしょう。

LauncherDiscoveryListenerTestExecutionListener API を使用することが多いのは、任意の形式のテスト結果レポートを生成したり、IDE 内の GUI でテストの実行計画を表示したりする場合です。 やはりビルドツールや IDE の提供する実装クラスを実行時に自動的に登録する場合が多いでしょう。 サードパーティライブラリを含む場合もあるようです(いずれにしても自動的に登録するようになっています)。 独自のリスナーを実装、登録することもできます。

リスナーを登録したり構成したりする方法については次のドキュメントを参照してください。

JUnit Platform は次のようなリスナーを提供しています。

Java Flight Recorder を使用する

FlightRecordingExecutionListenerFlightRecordingDiscoveryListener。 テストの探索時と実行時に Java Flight Recorder のイベントを発行します。

LegacyXmlReportGeneratingListener

TestExecutionListener が、JUnit 4 のテスト結果レポート形式として事実上の標準になっている XML 形式のテスト結果レポートを生成します。 詳しくは JUnit Platform レポート機能 を参照してください。

LoggingListener

TestExecutionListener の生成する全てのログメッセージを BiConsumer の消費するイベントとして生成します(ThrowableSupplier<String> として渡します)。

SummaryGeneratingListener

TestExecutionListener が実行したテストの概要を PrintWriter で(標準出力に)出力します。

UniqueIdTrackingListener

TestExecutionListener の実行する TestPlan がスキップおよび実行した全てのテストそれぞれに固有の ID を割り当てて追跡し、 TestPlan が完了したら追跡した全ての ID をファイルに記録します。

4.8.1. Java Flight Recorder を使用する

JUnit Platform は 1.7 から Java Flight Recorder のイベントを生成する機能をオプトイン方式で提供しています。 JEP 328 は Java Flight Recorder(JFR)を次のように説明しています。

Flight Recorder はアプリケーション(JVM や OS)の生成したイベントを記録します。 イベントは単一ファイルへ記録します。 イベントを記録したファイルはバグ報告へ添付したり、サポートエンジニアが調査するために使用したりできるので、問題が発生するまでに起きていたことを後から分析できます。

テストを実行しながら Flight Recorder イベントを記録するには次のような設定をしなければなりません。

  1. Java 8 Update 262 以降、あるいは Java 11 以降を使用します。

  2. org.junit.platform.jfr モジュール(junit-platform-jfr-1.9.0-SNAPSHOT.jar)を、テストランタイムの参照できるクラスパスやモジュールパスへ配置します。

  3. テストを実行するときにフライトレコード機能を有効化します。具体的には JVM のコマンドライン引数に次のようなフラグを指定します。

    -XX:StartFlightRecording:filename=...

適切なコマンドライン引数を構成するには、使用しているビルドツールのドキュメントを参照してください。

JDK と一緒に配布されているコマンドラインツールの jfr や、https://jdk.java.net/jmc/[JDK Mission Control] を使用すれば、記録したイベントを分析できます。

今のところ Flight Recorder のイベント記録機能は 実験的な機能です。 JUnit チームがこの機能を改善して、正式機能へ 昇格 できるよう、試用してフィードバックを提供してください。

5. 拡張モデル

5.1. 概要

JUnit 4 の拡張ポイントである RunnerTestRuleMethodRule に比べると、JUnit Jupiter の拡張モデル Extension API は単純で分かりやすい考え方になっています。 なお、Extension 自体はただのマーカーインターフェイスです。

5.2. 拡張機能を登録する

拡張機能を登録する方法は3種類あります。 @ExtendWith を使用する 宣言的な 登録と、@RegisterExtension を使用する 手続き的な 登録と、Java の ServiceLoader を使用する 自動的な 登録です。

5.2.1. 宣言的に登録する方法

開発者は @ExtendWith(…​) に(あるいは自作の 合成アノテーション に)登録する拡張機能のクラス参照を指定することで、テストインターフェイスやテストクラスやテストメソッドへ1つ以上の拡張機能を登録できます。 JUnit Jupiter の 5.8 からは、フィールドや、テストクラスのコンストラクタあるいはテストメソッドあるいはライフサイクルメソッドのメソッド引数を @ExtendWith で修飾できるようになりました。

例えば、WebServerExtension をテストメソッドへ登録するときは次のように記述します。 ここでは、WebServerExtension が HTTP サーバーを起動し、@WebServerUrl で修飾したメソッド引数へサーバーのURLを注入することを想定しています。

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

あるクラスやそのサブクラスに定義した全てのテストメソッドで使用するために WebServerExtension を登録するには、テストクラスを次のように修飾します。

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

@ExtendWith には複数の拡張機能(クラス参照)を登録できます。

@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })
class MyFirstTests {
    // ...
}

それぞれの拡張機能ごとに @ExtendWith を記述することもできます。

@ExtendWith(DatabaseExtension.class)
@ExtendWith(WebServerExtension.class)
class MySecondTests {
    // ...
}
拡張機能の登録順序

クラスやメソッドやメソッド引数について宣言的に登録した拡張は、ソースコードに記述したとおりの順番で登録されます。 例えば、前のコード例の MyFirstTestMySecondTests なら、どちらも DatabaseExtensionWebServerExtension見た目の順番通り に拡張機能を登録します。

複数の拡張機能の組み合わせを再利用できるようにしたければ、@ExtendWithメタアノテーション として使用する独自の 合成アノテーション を作成すればいいでしょう。 そうすれば、@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })@DatabaseAndWebServerExtension に置き換えられます。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })
public @interface DatabaseAndWebServerExtension {
}

前の例ではクラスやメソッドを @ExtendWith で修飾していました。 しかし、フィールドやメソッド引数に対して拡張機能を登録するほうが役に立つ場合があるのです。 乱数を生成する拡張機能があるとして、生成した乱数をフィールドやコンストラクタ引数、テストメソッドの引数、ライフサイクルメソッドの引数に注入できる場合を考えてみましょう。 なお、この拡張機能は @ExtendWith(RandomNumberExtension.class) をメタアノテーションとする @Random アノテーションを提供するものとします。 この拡張機能は次のように使用できるでしょう。

@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(RandomNumberExtension.class)
public @interface Random {
}
class RandomNumberDemo {

    // use random number field in test methods and @BeforeEach
    // or @AfterEach lifecycle methods
    @Random
    private int randomNumber1;

    RandomNumberDemo(@Random int randomNumber2) {
        // use random number in constructor
    }

    @BeforeEach
    void beforeEach(@Random int randomNumber3) {
        // use random number in @BeforeEach method
    }

    @Test
    void test(@Random int randomNumber4) {
        // use random number in test method
    }

}
フィールドを @ExtendWith で修飾した場合の拡張機能の登録順序

フィールドを @ExtendWith で修飾して宣言的に登録した拡張機能の登録順は、@RegisterExtension で修飾したフィールドや @ExtendWith で修飾した他のフィールドの相対的な関係性を考慮する、意図的にわかりにくくした決定論的アルゴリズムによって決まります。 しかし、@ExtendWith で修飾したフィールド(の登録する拡張機能)の順序は @Order アノテーションで変更できます。 詳しくは @RegisterExtension で修飾したフィールドに関する 拡張機能の登録順序 の説明を参照してください。

@ExtendWithstatic 宣言したフィールドとしなかったフィールドのどちらも修飾できます。 @RegisterExtension で修飾するフィールドを static 宣言する場合static 宣言しない場合 と同じ規則になります。

5.2.2. 手続き的に登録する方法

開発者はテストクラスのフィールドを @RegisterExtension で修飾することで、手続き的に拡張機能を登録できます。

@ExtendWith宣言的に登録する 場合、アノテーションを使用するしか設定を変更する方法がありませんでした。 しかし、@RegisterExtension手続き的に登録する 場合、拡張機能のコンストラクタや static 宣言したファクトリメソッドやビルダー API で設定を変更できます。

拡張機能の登録順序

初期設定では、@RegisterExtension で手続き的に登録する場合や、フィールドを @ExtendWith で修飾して宣言的に登録する場合、その登録順は意図的にわかりにくくした決定論的アルゴリズムで決まるようになっています。 したがって、再びテストスイートを実行しても、同じ登録順になることが保証されています。再現可能なビルド(プロセス)を保証するためです。 とはいえ、拡張機能の登録順序を明示したい場合もあるでしょう。 そういう場合、@RegisterExtension@ExtendWith で修飾したフィールドへ @Order を追加します。

@Order で修飾しなかったフィールドの相対位置は default すなわち Integer.MAX_VALUE / 2 になります。 @Order で修飾したフィールドによる拡張機能の登録順を、@Order で修飾しなかったフィールドによる拡張機能の前あるいは後にできるようにするためです。 default 値より小さい相対位置を指定したフィールド(の拡張機能)は、@Order で修飾しなかったフィールド(の拡張機能)より前に登録します。 同様に、default 値より大きい相対位置を指定したフィールド(の拡張機能)は、@Order で修飾しなかったフィールド(の拡張機能)より後に登録します。 例えば、事前処理 を提供する拡張機能を default 値より大きな相対位置で登録すれば最後に登録するようにできるし、事後処理 を提供する拡張機能を default 値より小さな相対位置で登録すれば最初に登録するようにできます。

@RegisterExtension で修飾するフィールドは決して null にしてはいけません(参照するとき)。static 宣言したフィールドとしなかったフィールドのどちらも修飾できます。
static 宣言したフィールドで拡張機能を登録する

@RegisterExtension で修飾するフィールドを static 宣言する場合、テストクラスを修飾する @ExtendWith より後に拡張機能を登録します。 このように クラスレベルで登録した拡張機能 は、どのような拡張APIでも実装できることになっています(制限はありません)。 クラスレベルのAPIである BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor, TestInstancePreDestroyCallback を実装できますし、メソッドレベルのAPIである BeforeEachCallback なども実装できるのです。

次のコード例では、テストクラスのフィールド server を、WebServerExtension のビルダー API で手続き的に初期化しています。 WebServerExtension は自動的にクラスレベルの拡張として登録されます。 ここでは、テストクラスの全てのテストを実行する前に HTTP サーバーを開始し、全てのテストが完了してから HTTP サーバーを停止することになります。 さらに、static 宣言したライフサイクルメソッド @BeforeAll@AfterAll は、@BeforeEach@AfterEach@Test と同じく server フィールドを介して拡張機能のインスタンスを参照できます。

static フィールドで拡張機能を登録する(Java)
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());
    }

}
Kotlin における static フィールドの扱い

Kotlin には static フィールドという考え方がありません。 しかし、@JvmStatic アノテーションを使うと、コンパイラに private static フィ-ルドを生成させられるようになっています。 public static フィールドを生成させたいときは @JvmField アノテーションを使用します。

次のコード例は前の WebServerDemo の Kotlin バージョンです。

static フィールドで拡張機能を登録する(Kotlin)
class KotlinWebServerDemo {

    companion object {
        @JvmStatic
        @RegisterExtension
        val server = WebServerExtension.builder()
                .enableSecurity(false)
                .build()
    }

    @Test
    fun getProductList() {
        // Use WebClient to connect to web server using serverUrl and verify response
        val webClient = WebClient()
        val serverUrl = server.serverUrl
        assertEquals(200, webClient.get("$serverUrl/products").responseStatus)
    }
}
インスタンスフィールドで拡張機能を登録する

@RegisterExtension で修飾したフィールドがインスタンスフィールドの場合、テストクラスをインスタンス化し、登録済みの全ての TestInstancePostProcessor が後処理を実行し(拡張機能が自身のインスタンスをフィールドに注入する可能性もあります)、それから拡張機能を登録します。 したがって インスタンスレベルの拡張機能BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor のような API を実装していたとしても、呼び出される機会はありません。 初期設定ではインスタンスレベルの拡張機能が登録されるのは、メソッドを修飾した @ExtendWith の拡張機能より になります。 ただし、テストクラスのセマンティクスをクラス単位(per-class)にしている場合、メソッドを修飾した @ExtendWith の拡張機能より に登録します。

次のコード例ではインスタンスフィールド docs を手続き的に初期化しています。 独自の lookUpDocsDir() メソッドの返り値を DocumentExtension のファクトリメソッド 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 の ServiceLoader を使用する 大局的な拡張機能の登録方法 に対応しています。 この方法を使用すると、クラスパスに配置したサードパーティの拡張機能を自動的に検出、登録することができます。

具体的には、拡張機能の実装と /META-INF/services/org.junit.jupiter.api.extension.Extension というファイルを jar ファイルに配置します。 org.junit.jupiter.api.extension.Extension には拡張機能の実装クラスの完全修飾クラス名を記述します。

拡張機能の自動検出を有効化する

自動検出機能は高度な機能なので、初期設定では無効化されています。 有効化するには 設定パラメータ junit.jupiter.extensions.autodetection.enabledtrue を設定します。 Launcher に渡す LauncherDiscoveryRequest の値を、システムプロパティや設定ファイル(設定パラメータの構成 を参照)で指定できます。

例えば、システムプロパティを指定するときは次のように実行します。

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

自動検出機能を有効にしている場合、JUnit Jupiter の基本拡張(TestInfoTestReporter のための拡張機能)を登録した後に、ServiceLoader の検出した拡張機能を登録します。

5.2.4. 拡張機能の継承

登録した拡張機能は、テストクラスの階層構造をトップダウン方向に継承していきます。 また、クラスレベルで登録した拡張機能は、メソッドレベルに継承していきます。 さらに、特定の拡張機能はその拡張コンテキストと親のコンテキストについて1度しか登録できません。 後から同じ拡張機能を登録しようとした結果は無視されます。

5.3. 条件付きのテスト実行

ExecutionCondition条件付きのテスト実行 を手続き的に実現する拡張APIです。

ExecutionCondition はそれぞれのテストコンテナ(テストクラス)ごとに 評価されExtensionContext よりそのコンテナに含まれる全てのテストを実行するかどうかを決定します。 同じように、ExecutionCondition はそれぞれのテストメソッドごとに 評価されExtensionContext そのテストメソッドを実行するかどうかを決定します。

複数の ExecutionCondition を登録した場合、どちらかの評価結果が disabled ならただちにそのテストコンテナ(あるいはテスト)を無効化します。 つまり、常に登録した全ての条件が評価されるわけではないということです(他の条件がテストを無効と判断しているかもしれません)。 ブール演算の OR 演算子のように短絡評価するのです。

具体例は DisabledCondition@Disabled のソースコードを参照してください。

5.3.1. 条件を不活性にする

想定した条件を 満たさない場合 でもテストスイートを実行できるようになっていると便利なときがあります。 例えば、成功しないことが分かっている としても @Disabled で修飾したテストを実行したい場合があるかもしれません。 そのためには、設定パラメータの junit.jupiter.conditions.deactivate で不活性にする(評価しない)条件を指定します。 Launcher に渡す LauncherDiscoveryRequest の値を、システムプロパティや設定ファイル(設定パラメータの構成 を参照)で指定できます。

例えば、@Disabled をシステムプロパティで不活性化するときは次のように実行します。

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

パターンマッチ記法

パターンマッチ記法 を参照してください。

5.4. テストインスタンスファクトリ

TestInstanceFactory はテストクラスのインスタンスを 作成する ための拡張APIです。

一般的な使い方として、DIフレームワークからテストインスタンスを取得したり、static ファクトリメソッドでテストインスタンスを作成することがあると思います。

TestInstanceFactory が登録されていなければ、テストフレームワークはテストクラスをインスタンス化するため 唯一のコンストラクタ を使用します。 ParameterResolver でコンストラクタ引数を解決する場合があるかもしれません。

TestInstanceFactory を実装した拡張機能を登録できるのは、テストインターフェイスやトップレベルクラス、@Nested で修飾した内部クラスです。

1つのテストクラスに対して複数の TestInstaneFactory を登録すると、そのクラスの全てのテストや、全ての派生クラス、全ての入れ子クラスについて例外をスローします。 既定クラスや エンクロージングクラス@Nested で修飾した内部クラス)が登録した TestInstanceFactory は全て 継承 します。 TestInstanceFactory が一意になることを保証するのはユーザーの仕事なのです。

5.5. テストインスタンスを作成した後の処理

TestInstancePostProcessor はテストクラスのインスタンスを作成した に実行する処理を実行するための拡張APIです。

一般的な使い方として、テストインスタンスに依存オブジェクトを注入したり、独自の初期化メソッドを呼び出すことがあると思います。

具体例は MockitoExtensionSpringExtension のソースコードを参照してください。

5.6. テストインスタンスを破棄する前のコールバック

TestInstancePreDestroyCallback はテストクラスのインスタンスがテストを実行した 、インスタンスを破棄する に実行したい処理を実行するための拡張APIです。

一般的な使い方として、テストインスタンスの依存オブジェクトを後始末したり、独自の逆初期化メソッドを呼び出すことがあると思います。

5.7. パラメータの解決

ParameterResolver は実行時にいろいろなパラメータを解決するための拡張APIです。

テストクラス のコンストラクタ、テストメソッドライフサイクルメソッドテストクラスとテストメソッド を参照)に宣言した引数は、ParameterResolver により実行時に 解決 しなければなりません。 使用できるのは組み込みの ParameterResolverTestInfoParameterResolver など)か、ユーザーの登録した拡張機能 が提供する ParameterResolver です。 一般的には、引数の 名前アノテーション、あるいはそれらの組み合わせから解決することになるでしょう。

総称型クラスのアダプターである TypeBasedParameterResolver を継承すると、特定の型に対する引数を解決する ParameterResolver を簡単に実装できます。

具体例は CustomTypeParameterResolverCustomAnnotationParameterResolverMapOfListsTypeBasedParameterResolver のソースコードを参照してください。

JDK 9 より前の JDK に付属する javac の生成するバイトコードに含まれるバグのせいで、インナークラス のコンストラクタ(@Nested で修飾したテストクラスです)引数を修飾するアノテーションを、java.lang.reflect.Parameter API で直接的に取得する操作は常に失敗します。

ParameterResolver の実装クラスは ParameterContext API (メソッドのリストは以下)を使用できるので、引数を修飾するアノテーションを参照できます。 拡張機能の作者は、JDK のバグを回避するため java.lang.refrect.Parameter の代わりにこれらの API を使うことを強くお勧めします。

  • boolean isAnnotated(Class<? extends Annotation> annotationType)

  • Optional<A> findAnnotation(Class<A> annotationType)

  • List<A> findRepeatableAnnotations(Class<A> annotationType)

5.8. テスト結果の処理

TestWatcherテストメソッド の実行結果を処理するための拡張APIです。 例えば、TestWatcher は実行時の状況に応じた次のようなイベントを受信します(メソッドを呼び出します)。

  • testDisabled: 無効化された テストメソッド をスキップしたときに呼び出すメソッドです

  • testSuccessful: テストメソッド の実行が正常に終了した後に呼び出すメソッドです

  • testAborted: テストメソッド の実行が中断された後に呼び出すメソッドです

  • testFailed: テストメソッド の実行が失敗した後に呼び出すメソッドです

テストクラスとテストメソッド における「テストメソッド」の定義とは違って、このセクションの テストメソッド@Test@TestTemplate で修飾したメソッドのことを指しています(@RepeatedTest@ParameterizedTest も含みます)。

TestWatcher の実装クラスを提供する拡張機能は、メソッドレベルかクラスレベルのいずれかで登録します。 拡張機能をクラスレベルで登録した場合、そこに含まれる全てのテストメソッドと、@Nested テストクラスに含まれる全てのテストメソッドが対象になります。

ExtensionContext の提供する Store に格納された全ての ExtensionContext.Store.CloseableResource のインスタンスは、この API より 前に 閉じられます(拡張機能の状態を保存する を参照)。 閉じると困るリソースがあるなら、親のコンテキストの Store を使用するといいでしょう。

5.9. ライフサイクルコールバック

次のインターフェイスはテストの実行時ライフサイクルにおけるさまざまな時点で呼び出される拡張APIです。 詳しくは、後に続くセクションの具体例や、それぞれのインターフェイスや org.junit.jupiter.api.extension パッケージの Javadoc を参照してください。

複数の拡張APIを実装する
拡張機能の作者は1つの実装クラスで複数の拡張API(インターフェイス)を実装できます。 具体例として SpringExtension のソースコードを参照してください。

5.9.1. テスト実行の前、および、後のコールバック

BeforeTestExecutionCallbackAfterTestExecutionCallback は、(相対的に)テストメソッドを実行する 直前 あるいは 直後 に任意の振る舞いを追加する拡張APIです。 時間を計測したり、トレースを記録したりするような場面で役立ちます。 @BeforeEach メソッドの前後や、@AfterEach メソッドの前後に振る舞いを追加したいときは、BeforeEachCallbackAfterEachCallback を実装します。

次のコード例では、テストメソッドの実行時間を計算してログに出力するため、TimingExtensionBeforeTestExecutionCallbackAfterTestExecutionCallback を実装しています。

テストメソッドの実行時間を計算してログに出力する拡張機能
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 では TimingExtension@ExtendWith で登録しているので、それぞれのテストメソッドの実行時間が計測されています。

TimingExtension を使用するテストクラス
@ExtendWith(TimingExtension.class)
class TimingExtensionTests {

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

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

}

実行すると、次のような出力を得られるでしょう。

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

5.10. 例外処理

テストを実行しているときに例外が発生したら、それを呼び出し元に伝播させる前に介入して、なんらかの処理をさせたい場合があります(エラーをログに出力したり、リソースを解法したり)。 JUnit Jupiter ではそのような拡張機能を作成するための拡張APIを提供しています。 @Test メソッドで発生した例外を処理する TestExecutionExceptionHandler と、ライフサイクルメソッド(@BeforeAll, @BeforeEach, @AfterEach, @AfterAll)で発生した例外を処理する LifecycleMethodExecutionExceptionHandler です。

次のコード例では、テストを実行しているときに発生した IOException 以外の例外を再スローする拡張機能を実装しています。

テスト実行時に発生した IOException を隠す例外処理ハンドラーの拡張機能
public class IgnoreIOExceptionExtension implements TestExecutionExceptionHandler {

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

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

次のコード例では、準備や後始末をしているときに予期せぬ例外が発生した場合、テスト対象のアプリケーションの状態を記録する方法を示しています。 ライフサイクルメソッドのコールバックを使用する場合と違って、例外処理ハンドラーを実行するかどうかは実行したテストの内容によって異なります。 この例のような使い方をすると、@BeforeALl, @BeforeEach, @AfterEach, @AfterAll が失敗した直後に呼び出されることが保証されています。

An exception handling extension that records application state on error
class RecordStateOnErrorExtension implements LifecycleMethodExecutionExceptionHandler {

    @Override
    public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex)
            throws Throwable {
        memoryDumpForFurtherInvestigation("Failure recorded during class setup");
        throw ex;
    }

    @Override
    public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex)
            throws Throwable {
        memoryDumpForFurtherInvestigation("Failure recorded during test setup");
        throw ex;
    }

    @Override
    public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable ex)
            throws Throwable {
        memoryDumpForFurtherInvestigation("Failure recorded during test cleanup");
        throw ex;
    }

    @Override
    public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable ex)
            throws Throwable {
        memoryDumpForFurtherInvestigation("Failure recorded during class cleanup");
        throw ex;
    }
}

同じライフサイクルメソッドの定義順によって、複数の例外処理ハンドラーを呼び出す可能性があります。 いずれかの例外処理ハンドラーが自分で処理した例外を隠してしまうと、後に続く例外処理ハンドラーは呼び出されなくなりますし、JUnit にも失敗が伝わりません。 例外が発生しなかったとように扱われてしまうのです。 例外処理ハンドラーは受け取った例外を再スローするか、(受け取った例外を内包する)新しい例外をスローするか選ぶことになるでしょう。

@BeforeAll, @AfterAll で発生した例外を、LifecycleMethodExecutionExceptionHandler を実装した拡張機能で扱いたいときは、拡張機能をクラスレベルで登録しなければなりません。 この場合、それぞれのテストメソッドに対する BeforeEach, AfterEach にも例外処理ハンドラーが登録されます。

複数の例外処理ハンドラーを登録する
// Register handlers for @Test, @BeforeEach, @AfterEach as well as @BeforeAll and @AfterAll
@ExtendWith(ThirdExecutedHandler.class)
class MultipleHandlersTestCase {

    // Register handlers for @Test, @BeforeEach, @AfterEach only
    @ExtendWith(SecondExecutedHandler.class)
    @ExtendWith(FirstExecutedHandler.class)
    @Test
    void testMethod() {
    }

}

5.11. テストコードの呼び出しへの割り込み

InvocationInterceptor はテストコードの呼び出しに割り込んで処理をさせたいときに使用する拡張APIです。

次のコード例では、Swing のイベントディスパッチスレッドで全てのテストメソッドを実行しています。

ユーザー定義スレッドでテストを実行する拡張機能
public class SwingEdtInterceptor implements InvocationInterceptor {

    @Override
    public void interceptTestMethod(Invocation<Void> invocation,
            ReflectiveInvocationContext<Method> invocationContext,
            ExtensionContext extensionContext) throws Throwable {

        AtomicReference<Throwable> throwable = new AtomicReference<>();

        SwingUtilities.invokeAndWait(() -> {
            try {
                invocation.proceed();
            }
            catch (Throwable t) {
                throwable.set(t);
            }
        });
        Throwable t = throwable.get();
        if (t != null) {
            throw t;
        }
    }
}

5.12. テストテンプレートに呼び出し元のコンテキストを与える

@TestTemplate で修飾したメソッドを実行するには、少なくとも1つの TestTemplateInvocationContextProvider を登録しなければなりません。 このインターフェイスの実装クラスは TestTemplateInvocationContext を要素とする Stream を提供しなければなりません。 それぞれの要素には、独自の表示名や、直後の @TestTemplate メソッドを呼び出すときにだけ使用する拡張機能のリストが含まれています。

次のコード例では、テストテンプレートの書き方と、TestTemplateInvocationContextProvider の実装方法を説明しています。

自作の拡張機能とテストテンプレート
final List<String> fruits = Arrays.asList("apple", "banana", "lemon");

@TestTemplate
@ExtendWith(MyTestTemplateInvocationContextProvider.class)
void testTemplate(String fruit) {
    assertTrue(fruits.contains(fruit));
}

public class MyTestTemplateInvocationContextProvider
        implements TestTemplateInvocationContextProvider {

    @Override
    public boolean supportsTestTemplate(ExtensionContext context) {
        return true;
    }

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
            ExtensionContext context) {

        return Stream.of(invocationContext("apple"), invocationContext("banana"));
    }

    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回呼び出されるはずです。 それぞれの呼び出しについて、呼び出しコンテキストに指定された表示名は applebanana になります。 また、メソッド引数を解決する独自の ParameterResolver も登録されています。 ConsoleLauncher でテストを実行すると、次のように出力されるでしょう。

└─ testTemplate(String) ✔
   ├─ apple ✔
   └─ banana ✔

TestTemplateInvocationContextProvider API の主な目的は、さまざまなコンテキストで呼び出されるメソッドのように、繰り返し呼び出されることが前提の普通とは異なるテストを実現することです。 例えば、毎回違うパラメータで実行したり、テストクラスのインスタンスの状態を毎回変化させたり、状態を変化させずに何度も実行するなどです。 この API で実現されてる 繰り返しテストパラメタライズテスト についても参照してください。

5.13. 拡張機能の状態を保存する

普通なら拡張機能をインスタンス化するのは1度だけです。 だから「拡張機能が前に呼び出されたときの状態を残すにはどうしたらいいのか」という疑問が生じるのは当然です。 ExtensionContext API の Store インターフェイスはそのために使用します。 拡張機能が後から参照する値を登録するための API なのです。 メソッドレベルのスコープで Store を使用する具体例として TimingExtension を見てください。 テストメソッドを実行しているときに ExtensionContext へ登録した値は、周辺の ExtensionContext からは参照できないことに注意してください。 ExtensionContext は入れ子構造にできるのですが、内側の可視性は制限されているのです。 値を登録したり参照したりするためのメソッドについては Store の Javadoc を参照してください。

ExtensionContext.Store.CloseableResource
拡張機能用コンテキストストアは、その拡張機能のコンテキストのライフサイクルに束縛されています。 その拡張機能のライフサイクルが終了になれば、結びつけられたストアも閉じられるのです。 登録したあらゆる CloseableResource のインスタンスに対して、登録した順序の逆順に close() メソッドを呼び出していきます。

5.14. 拡張機能で利用できるユーティリティ

junit-platform-commons アーティファクトの提供する org.junit.platform.commons.support パッケージには、アノテーションやクラスやリフレクションに関連する操作や、クラスパスを探索する機能に関する、保守されている 便利メソッドが含まれています。 テスト実行エンジンや拡張機能の作者は、JUnit Platform と一貫性のある操作を利用できるのです。

5.14.1. アノテーション

AnnotationSupport は、アノテーションで修飾できる要素(パッケージ、アノテーション、クラス、インターフェイス、コンストラクタ、メソッド、フィールド)を操作する便利メソッドを提供します(static メソッドです)。 アノテーションで修飾されているか、任意のアノテーションでメタアノテーションされているか、特定のアノテーションの探索、アノテーションで修飾されたメソッド、フィールド、クラス、インターフェイスの探索など。 クラスの実装したインターフェイスや、クラス階層を渡り歩いてアノテーションを探索するメソッドもあります。 詳しくは AnnotationSupport の Javadoc を参照してください。

5.14.2. クラス

ClassSupport は、クラス(java.lang.Class のインスタンスなど)を操作する便利メソッドを提供します(static メソッドです)。 詳しくは ClassSupport の Javadoc を参照してください。

5.14.3. リフレクション

ReflectionSupport は JDK の標準ライブラリが提供するリフレクション API やクラスロードの仕組みを拡張する便利メソッドを提供します(static メソッドです)。 クラスパスから任意の述語式にマッチするクラスを探索する、指定したクラスをロードして新しいインスタンスを作成する、メソッドを探索して実行する(クラス階層を渡り歩くなど)、など。 詳しくは ReflectionSupport の Javadoc を参照してください。

5.14.4. 修飾子(Modifier)

ModifierSupport はクラスのメンバーやクラス自体の修飾子を操作する便利メソッドを提供します。 メンバーの可視性が public/private/abstract/static のどれなのか、など。 詳しくは ModifierSupport の Javadoc を参照してください。

5.15. ユーザーコードと拡張機能の相対的な実行順序

1つ以上のテストメソッドを持つテストクラスを実行するとき、ユーザーの記述したテストとライフサイクルメソッドに加えて、いくつもの拡張機能(コールバック)が呼び出されることになります。

テストの実行順序 も参照してください。

5.15.1. ユーザーコードと拡張機能の順序関係

次の図はユーザーコードと拡張機能の相対的な順序を示しています。 ユーザーの記述したテストとライフサイクルメソッドはオレンジ色、拡張機能が実装したコールバックは青色です。 灰色は1つのテストメソッドを実行するとき、および、全てのテストメソッドについて繰り返し実行するときの範囲を示しています。

extensions lifecycle
ユーザーコードと拡張機能の順序関係

次の表は ユーザーコードと拡張機能の順序関係 を実行する16の手順を詳しく記述したものです。

手順 インターフェイス/アノテーション 説明

1

インターフェイス org.junit.jupiter.api.extension.BeforeAllCallback

拡張機能:テストコンテナのあらゆるテストメソッドより先に実行する

2

アノテーション org.junit.jupiter.api.BeforeAll

ユーザーコード:テストコンテナのあらゆるテストメソッドより先に実行する

3

インターフェイス org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleBeforeAllMethodExecutionException

拡張機能:@BeforeAll メソッドがスローした例外を処理する例外処理ハンドラーを実行する

4

インターフェイス org.junit.jupiter.api.extension.BeforeEachCallback

拡張機能:テストコンテナのそれぞれのテストメソッドを実行する前に実行する

5

アノテーション org.junit.jupiter.api.BeforeEach

ユーザーコード:テストコンテナのそれぞれのテストメソッドを実行する前に実行する

6

インターフェイス org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleBeforeEachMethodExecutionException

拡張機能:@BeforeEach メソッドがスローした例外を処理する例外処理ハンドラーを実行する

7

インターフェイス org.junit.jupiter.api.extension.BeforeTestExecutionCallback

拡張機能:テストコンテナのそれぞれのテストメソッドを実行する直前に実行する

8

アノテーション org.junit.jupiter.api.Test

ユーザーコード:テストコンテナのそれぞれのテストメソッドを実行する

9

インターフェイス org.junit.jupiter.api.extension.TestExecutionExceptionHandler

拡張機能:テストメソッドがスローした例外を処理する例外処理ハンドラーを実行する

10

インターフェイス org.junit.jupiter.api.extension.AfterTestExecutionCallback

拡張機能:テストコンテナのそれぞれのテストメソッドを実行した直後、あるいは、例外処理ハンドラーを実行した直後に実行する

11

アノテーション org.junit.jupiter.api.AfterEach

ユーザーコード:テストコンテナのそれぞれのテストメソッドを実行した後に実行する

12

インターフェイス org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleAfterEachMethodExecutionException

拡張機能:@AfterEach メソッドがスローした例外を処理する例外処理ハンドラーを実行する

13

インターフェイス org.junit.jupiter.api.extension.AfterEachCallback

拡張機能:テストコンテナのそれぞれのテストメソッドより後に実行する

14

アノテーション org.junit.jupiter.api.AfterAll

ユーザーコード:テストコンテナの全てのテストメソッドより後に実行する

15

インターフェイス org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleAfterAllMethodExecutionException

拡張機能:@AfterAll メソッドがスローした例外を処理する例外処理ハンドラーを実行する

16

インターフェイス org.junit.jupiter.api.extension.AfterAllCallback

拡張機能:テストコンテナの全てのテストメソッドより後に実行する

単純な場合はテストメソッドを実行するだけでしょう(手順8)。 他の手順を実行するかどうかは、ユーザーコードや拡張機能が、対応するライフサイクルメソッドを提供しているかどうかによって決まります。 ライフサイクルメソッドのコールバックについては、対応するインターフェイスや拡張機能の Javadoc を参照してください。

全てのユーザーコード(メソッド)の呼び出しは、InvocationInterceptor で割り込み可能です。

5.15.2. コールバックによる包み隠す振る舞い

JUnit Jupiter は、複数のライフサイクルコールバック(BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback)を登録した場合、それぞれの呼び出しを 包み隠す ことを保証します。

例えば、Extension1Extension2 を順番に登録した場合、Extension1 の提供する全ての "before" コールバックは、Extension2 の提供する全ての "before" コールバックより 先に 実行することが保証されています。 同様に、Extension1 の提供する全ての "after" コールバックは、Extension2 の提供する全ての "after" コールバックより 後に 実行することが保証されています。 この関係を「Extension1Extension2包み隠す」と呼んでいます。

ユーザーコードの提供する ライフサイクルメソッドテストクラスとテストメソッド を参照)は、クラス階層やインターフェイス階層についても 包み隠す 関係になることが保証されています。

  • 基底クラスから継承した @BeforeAll メソッドは 隠蔽オーバーライド もできません。 さらに、派生クラスの @BeforeAll メソッドより 先に 実行します。

    • 同様に、インターフェイスから継承した @BeforeAll メソッドは 隠蔽オーバーライド もできません。 そして、実装クラスの @BeforeAll メソッドより 先に 実行します。

  • 基底クラスから継承した @AfterAll メソッドは 隠蔽オーバーライド もできません。 さらに、派生クラスの @AfterAll メソッドより 後に 実行します。

    • 同様に、インターフェイスから継承した @AfterAll メソッドは 隠蔽オーバーライド もできません。 そして、実装クラスの @AfterAll メソッドより 後に 実行します。

  • 基底クラスから継承した @BeforeEach メソッドは オーバーライド できません。 さらに、派生クラスの @BeforeEach メソッドより 先に 実行します。

    • 同様に、インターフェイスから継承した @BeforeEach メソッドは オーバーライド できません。 そして、実装クラスの @BeforeEach メソッドより 先に 実行します。

  • 基底クラスから継承した @AfterEach メソッドは オーバーライド できません。 さらに、派生クラスの @AfterEach メソッドより 後に 実行します。

    • 同様に、インターフェイスから継承した @AfterEach メソッドは オーバーライド できません。 そして、実装クラスの @AfterEach メソッドより 後に 実行します。

次のコード例はここまでに説明した振る舞いを示しています。 具体的になんらかの処理するようにはなっていませんが、テストがデータベースとやりとりする一般的なシナリオを再現しています。 Logger クラスから static インポートしているメソッドは、実行時の情報をログに出力するようになっているため、ユーザーコードと拡張機能のどちらで実装しているコールバックなのか分かりやすくなっています。

Extension1
import static example.callbacks.Logger.afterEachCallback;
import static example.callbacks.Logger.beforeEachCallback;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class Extension1 implements BeforeEachCallback, AfterEachCallback {

    @Override
    public void beforeEach(ExtensionContext context) {
        beforeEachCallback(this);
    }

    @Override
    public void afterEach(ExtensionContext context) {
        afterEachCallback(this);
    }

}
Extension2
import static example.callbacks.Logger.afterEachCallback;
import static example.callbacks.Logger.beforeEachCallback;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class Extension2 implements BeforeEachCallback, AfterEachCallback {

    @Override
    public void beforeEach(ExtensionContext context) {
        beforeEachCallback(this);
    }

    @Override
    public void afterEach(ExtensionContext context) {
        afterEachCallback(this);
    }

}
AbstractDatabaseTests
import static example.callbacks.Logger.afterAllMethod;
import static example.callbacks.Logger.afterEachMethod;
import static example.callbacks.Logger.beforeAllMethod;
import static example.callbacks.Logger.beforeEachMethod;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;

/**
 * Abstract base class for tests that use the database.
 */
abstract class AbstractDatabaseTests {

    @BeforeAll
    static void createDatabase() {
        beforeAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".createDatabase()");
    }

    @BeforeEach
    void connectToDatabase() {
        beforeEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".connectToDatabase()");
    }

    @AfterEach
    void disconnectFromDatabase() {
        afterEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".disconnectFromDatabase()");
    }

    @AfterAll
    static void destroyDatabase() {
        afterAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".destroyDatabase()");
    }

}
DatabaseTestsDemo
import static example.callbacks.Logger.afterEachMethod;
import static example.callbacks.Logger.beforeAllMethod;
import static example.callbacks.Logger.beforeEachMethod;
import static example.callbacks.Logger.testMethod;

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.Test;
import org.junit.jupiter.api.extension.ExtendWith;

/**
 * Extension of {@link AbstractDatabaseTests} that inserts test data
 * into the database (after the database connection has been opened)
 * and deletes test data (before the database connection is closed).
 */
@ExtendWith({ Extension1.class, Extension2.class })
class DatabaseTestsDemo extends AbstractDatabaseTests {

    @BeforeAll
    static void beforeAll() {
        beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".beforeAll()");
    }

    @BeforeEach
    void insertTestDataIntoDatabase() {
        beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()");
    }

    @Test
    void testDatabaseFunctionality() {
        testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()");
    }

    @AfterEach
    void deleteTestDataFromDatabase() {
        afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()");
    }

    @AfterAll
    static void afterAll() {
        beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".afterAll()");
    }

}

DatabaseTestsDemo を実行すると、次のように出力されるでしょう。

@BeforeAll AbstractDatabaseTests.createDatabase()
@BeforeAll DatabaseTestsDemo.beforeAll()
  Extension1.beforeEach()
  Extension2.beforeEach()
    @BeforeEach AbstractDatabaseTests.connectToDatabase()
    @BeforeEach DatabaseTestsDemo.insertTestDataIntoDatabase()
      @Test DatabaseTestsDemo.testDatabaseFunctionality()
    @AfterEach DatabaseTestsDemo.deleteTestDataFromDatabase()
    @AfterEach AbstractDatabaseTests.disconnectFromDatabase()
  Extension2.afterEach()
  Extension1.afterEach()
@BeforeAll DatabaseTestsDemo.afterAll()
@AfterAll AbstractDatabaseTests.destroyDatabase()

次のシーケンス図は DatabaseTestDemo を実行したとき、JupiterTestEngine の内部で行われている処理を(大部分省略して)示したものです。

extensions DatabaseTestsDemo
DatabaseTestsDemo

JUnit Jupiter では 単独の クラスやインターフェイスに宣言された複数のライフサイクルメソッドの実行順序を保証 しません。 一見するとアルファベット順に見えるかもしれませんが、正確にはそうではありません。 @Test メソッドの実行順序と同じようになっているのです。

単独の クラスやインターフェイスに宣言した複数のライフサイクルメソッドの実行順序は、意図的に分かりにくくした決定論的アルゴリズムによって決まります。 したがって、再びテストスイートを実行しても、同じ登録順になることが保証されています。再現可能なビルド(プロセス)を保証するためです。

さらに、単独の クラスやインターフェイスに宣言した複数のライフサイクルメソッドが 包み隠す 関係になることを保証 しません

次のコード例は、テストクラスに定義したライフサイクルメソッドの(間違った)実行順序に依存しているせいでいろいろ 壊れています

  • データベース接続を open() する 前に テストデータを insert しています。おそらくデータベース接続エラーが発生するでしょう。

  • テストデータを delete する 前に データベース接続を close() しています。おそらくデータベース接続エラーが発生するでしょう。

BrokenLifecycleMethodConfigDemo
import static example.callbacks.Logger.afterEachMethod;
import static example.callbacks.Logger.beforeEachMethod;
import static example.callbacks.Logger.testMethod;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

/**
 * Example of "broken" lifecycle method configuration.
 *
 * <p>Test data is inserted before the database connection has been opened.
 *
 * <p>Database connection is closed before deleting test data.
 */
@ExtendWith({ Extension1.class, Extension2.class })
class BrokenLifecycleMethodConfigDemo {

    @BeforeEach
    void connectToDatabase() {
        beforeEachMethod(getClass().getSimpleName() + ".connectToDatabase()");
    }

    @BeforeEach
    void insertTestDataIntoDatabase() {
        beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()");
    }

    @Test
    void testDatabaseFunctionality() {
        testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()");
    }

    @AfterEach
    void deleteTestDataFromDatabase() {
        afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()");
    }

    @AfterEach
    void disconnectFromDatabase() {
        afterEachMethod(getClass().getSimpleName() + ".disconnectFromDatabase()");
    }

}

BrokenLifecycleMethodConfigDemo を実行すると、次のように出力されるでしょう。

Extension1.beforeEach()
Extension2.beforeEach()
  @BeforeEach BrokenLifecycleMethodConfigDemo.insertTestDataIntoDatabase()
  @BeforeEach BrokenLifecycleMethodConfigDemo.connectToDatabase()
    @Test BrokenLifecycleMethodConfigDemo.testDatabaseFunctionality()
  @AfterEach BrokenLifecycleMethodConfigDemo.disconnectFromDatabase()
  @AfterEach BrokenLifecycleMethodConfigDemo.deleteTestDataFromDatabase()
Extension2.afterEach()
Extension1.afterEach()

次のシーケンス図は BrokenLifecycleMethodConfigDemo を実行したとき、JupiterTestEngine の内部で行われている処理を(大部分省略して)示したものです。

extensions BrokenLifecycleMethodConfigDemo
BrokenLifecycleMethodConfigDemo

このセクションで説明した振る舞いが前提になるため、JUnit チームとしては、テストクラスやテストインターフェイスに定義するライフサイクルメソッド(テストクラスとテストメソッド を参照)は、それぞれの種類ごとに1つだけ定義するようにして、お互いに依存しないようにすることをお勧めします。

6. 高度な使い方

6.1. JUnit Platform Launcher API

JUnit 5 の特徴的な目標の1つとして、ビルドツールや IDE などのプログラムから JUnit を操作する強力かつ安定したインターフェイスを提供することが挙げられます。 目的は、テストを探索したり実行したりする内部動作と、外部から指定するテストをふるい落とす条件やその他の設定を完全に分離することです。

JUnit 5 では Launcher という考え方を導入しました。 テストを探索し、選抜し、実行する要素です。 さらに、専用の TestEngine を使用すれば、Spock や Cucumber や FitNesse のようなサードパーティライブラリを JUnit Platform で実行できます。

Launcher API は junit-platform-launcher モジュールに含まれています。

実際に Launcher API を使用している好例は junit-platform-console プロジェクトの ConsoleLauncher です。

6.1.1. テストの発見

テストの発見 を JUnit Platform から独立した機能にすることで、旧バージョンの JUnit によるテストクラスやテストメソッドの特定に悩まされてきたビルドツールや 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 java.io.PrintWriter;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryListener;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.LauncherSessionListener;
import org.junit.platform.launcher.PostDiscoveryFilter;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;
import org.junit.platform.launcher.core.LauncherConfig;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import org.junit.platform.launcher.listeners.TestExecutionSummary;
import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener;
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
    .selectors(
        selectPackage("com.example.mytests"),
        selectClass(MyTestClass.class)
    )
    .filters(
        includeClassNamePatterns(".*Tests")
    )
    .build();

try (LauncherSession session = LauncherFactory.openSession()) {
    TestPlan testPlan = session.getLauncher().discover(request);

    // ... discover additional test plans or execute tests
}

クラスやメソッドを選択したり、パッケージの全てのクラスを選択したり、クラスパスやモジュールパスから全てのテストクラスを検索したりできるのです。 テストの発見は、存在する全てのテスト実行エンジンが行います。

発見した結果は、読み取り専用で階層的な木構造構造のテスト計画(TestPlan)になります。 データ構造の要素は LauncherDiscoveryRequest に適合したテスト実行エンジン、テストクラス、テストメソッドです。 API のクライアントは木を行き来して節を特定し、詳細情報を取得し、取得元(クラスやメソッド、ファイル中の位置など)の情報を取得します。 テスト計画に含まれる全ての節には 固有のID が割り当てられており、一部のテストやテストのグループを実行するために使用できます。

テストを発見している途中に発生したイベントから何らかの洞察を得るため、API のクライアントは1つ以上の LauncherDiscoveryListener を登録できます(LauncherDiscoveryRequestBuilder でインスタンスを作成します)。 初期設定では LauncherDiscoveryRequestBuilder が「中断および失敗」リスナーを登録します。 テストの発見に失敗したら、ただちに発見処理全体を中断するためです。 設定パラメータの junit.platform.discovery.listener.default に完全修飾クラス名を指定すれば変更できます。

6.1.2. テストの実行

テストを実行するには発見フェーズと同じ LauncherDiscoveryRequest を使用するか、新しい要求を作成して使用します。 進行状況や結果を報告するには1つ以上の TestExecutionListener を登録します。

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

SummaryGeneratingListener listener = new SummaryGeneratingListener();

try (LauncherSession session = LauncherFactory.openSession()) {
    Launcher launcher = session.getLauncher();
    // Register a listener of your choice
    launcher.registerTestExecutionListeners(listener);
    // Discover tests and build a test plan
    TestPlan testPlan = launcher.discover(request);
    // Execute test plan
    launcher.execute(testPlan);
    // Alternatively, execute the request directly
    launcher.execute(request);
}

TestExecutionSummary summary = listener.getSummary();
// Do something with the summary...

execute() メソッドには返り値がありません。 代わりに TestExecutionListener でテストの結果を集約できるようになっています。 具体的には SummaryGeneratingListenerLegacyXmlReportGeneratingListenerUniqueIdTrackingListener のソースコードを参照してください。

6.1.3. テスト実行エンジンの登録

JUnit は3種類の TestEngine の実装クラスを提供しています。

  • junit-jupiter-engine: JUnit Jupiter の基本となるエンジンです

  • junit-vintage-engine: 骨董品 のようなテストを JUnit Platform で実行するための、JUnit 4 を薄いレイヤーで包み込んだエンジンです

  • junit-platform-suite-engine: JUnit Platform で宣言的に構成したテストスイートを実行するエンジンです

サードパーティライブラリの作者は junit-platform-engine モジュールの TestEngine インターフェイスの実装クラスを提供し、登録 させることができます。 テスト実行エンジンは Java の ServiceLoader で登録します。 例えば、junit-jupiter-engine モジュールは org.junit.jupiter.engine.JupiterTestEngine を登録するのですが、それは jar ファイルの /META-INF/services/org.junit.platform.engine.TestEngineorg.junit.jupiter.engine.JupiterTestEngine と記述されているからです。

HierarchicalTestEngine を抽象基底クラスとして使用すると、テストを発見するロジックだけを実装すれば済むので便利です(junit-jupiter-engine でも使用しています)。 HierarchicalTestEngineNode インターフェイスを実装いた TestDescriptors を実装するようになっており、並列実行にも対応しています。
接頭辞が junit- のテスト実行エンジン ID は JUnit チームのために予約済みです

Launcher API は JUnit チームの公開する TestEngine 実装の ID だけが junit- で始まることを強制します。

  • サードパーティライブラリのテストエンジンの報告する ID が junit-jupiterjunit-vintage なら、JUnit Platform が例外をスローし、テストの実行はただちに中断します。

  • サードパーティライブラリのテストエンジンの報告する ID が junit- から始まる場合、JUnit Platform は警告をログ出力します。将来のバージョンでは例外をスローさせるかもしれません。

6.1.4. テスト発見後処理フィルター(PostDiscoveryFilter) を登録する

Lancher API へ渡す LauncherDiscoveryRequest にテスト発見後処理フィルターを設定するだけでなく、Java の ServiceLoader を使用して PostDiscoveryFilter の実装クラスを発見し、自動的に Launcher へ登録できます。

例えば、PostDiscoveryFilter を実装した example.CustomTagFilter (の完全修飾クラス名)を /META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter に記述しておけば、自動的に登録するのです。

6.1.5. セッションリスナー(LauncherSessionListener)を登録する

LauncherSessionListener の実装クラスを登録すると、LauncherSession を開いたとき(Launcher が最初にテストを発見し、実行する前)や、閉じるとき(それ以上発見したり実行したりするテストが無くなったとき)に通知します。 LauncherFactory へ渡す LauncherConfig で手続き的に登録することもできますし、Java の ServiceLoader を使用して自動的に登録することもできます(自動登録機能が有効なら)。

LauncherSessionListener は JVM が1回だけ setupteardown を実行する状況に最適化されています。 1つのセッションで最初のテストを発見する前と、最後のテストが終了した後に呼び出されるからです。 セッションの有効範囲は使用している IDE やビルドツールによって異なりますが、基本的にはテストを実行する JVM のライフサイクルと一致します。 次のコード例は、最初のテストを発見する前に HTTP サーバーを起動し、最後のテストが終了した後に HTTP サーバーを停止するリスナーです。

src/test/java/example/session/GlobalSetupTeardownListener.java
package example.session;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.sun.net.httpserver.HttpServer;

import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.LauncherSessionListener;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;

public class GlobalSetupTeardownListener implements LauncherSessionListener {

    private Fixture fixture;

    @Override
    public void launcherSessionOpened(LauncherSession session) {
        // Avoid setup for test discovery by delaying it until tests are about to be executed
        session.getLauncher().registerTestExecutionListeners(new TestExecutionListener() {
            @Override
            public void testPlanExecutionStarted(TestPlan testPlan) {
                if (fixture == null) {
                    fixture = new Fixture();
                    fixture.setUp();
                }
            }
        });
    }

    @Override
    public void launcherSessionClosed(LauncherSession session) {
        if (fixture != null) {
            fixture.tearDown();
            fixture = null;
        }
    }

    static class Fixture {

        private HttpServer server;
        private ExecutorService executorService;

        void setUp() {
            try {
                server = HttpServer.create(new InetSocketAddress(0), 0);
            }
            catch (IOException e) {
                throw new UncheckedIOException("Failed to start HTTP server", e);
            }
            server.createContext("/test", exchange -> {
                exchange.sendResponseHeaders(204, -1);
                exchange.close();
            });
            executorService = Executors.newCachedThreadPool();
            server.setExecutor(executorService);
            server.start(); (1)
            int port = server.getAddress().getPort();
            System.setProperty("http.server.port", String.valueOf(port)); (2)
        }

        void tearDown() {
            server.stop(0); (3)
            executorService.shutdownNow();
        }
    }

}
1 HTTP サーバーを起動します
2 テストから参照するための待ち受けポート番号を動的に決定し、システムプロパティへ設定します
3 HTTP サーバーを停止します

このサンプルコードでは JDK に付属している jdk.httserver モジュールの実装を使用していますが、他の実装でも同じように記述できるでしょう。 リスナーを自動的に JUnit Platform へ登録するには、次のようなリソースファイルをテストランタイムクラスパスに配置します(src/test/resources などに配置することになるでしょう)。

src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener
example.session.GlobalSetupTeardownListener

テストは次のように記述できます。

src/test/java/example/session/HttpTests.java
package example.session;

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

import java.net.HttpURLConnection;
import java.net.URL;

import org.junit.jupiter.api.Test;

class HttpTests {

    @Test
    void respondsWith204() throws Exception {
        String port = System.getProperty("http.server.port"); (1)
        URL url = new URL("http://localhost:" + port + "/test");

        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        int responseCode = connection.getResponseCode(); (2)

        assertEquals(204, responseCode); (3)
    }
}
1 リスナーの設定したシステムプロパティから待ち受けポート番号を読み取ります
2 HTTP サーバーにリクエストを送信します
3 レスポンスのステータスコードを確認します

6.1.6. テスト発見リスナー(LauncherDiscoveryListener)を登録します

Lancher API へ渡す LauncherDiscoveryRequest にテスト発見後処理フィルターを設定するだけでなく、Java の ServiceLoader を使用して LauncherDiscoveryListener の実装クラスを自動的に発見し、LauncherFactory で作成した Launcher へ登録できます。

例えば、LauncherDiscoveryListener を実装した example.CustomLauncherDiscoveryListener (の完全修飾クラス名)を /META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener に記述しておけば、自動的に登録するのです。

6.1.7. テスト実行リスナー(TestExecutionListener)を登録する

Launcher API のメソッドで手続き的にテスト実行リスナーを登録するだけでなく、Java の ServiceLoader を使用して TestExecutionListener の実装クラスを自動的に発見し、LauncherFactory で作成した Launcher へ登録できます。

例えば、TestExecutionListener を実装した example.CustomTestExecutionListener (の完全修飾クラス名)を /META-INF/services/org.junit.platform.launcher.TestExecutionListener に記述しておけば、自動的に登録するのです。

6.1.8. テスト実行リスナーの設定

TestExecutionListenerLauncher API で手続き的に登録するときは、手続き的にリスナーの設定を変更できます。 例えばコンストラクタやセッターメソッドを使う方法です。 しかし、ServiceLoader で自動的に登録するときは(テスト実行リスナー(TestExecutionListener)を登録する を参照)、直接的にリスナーの設定を変更する方法がありません。 そういう場合、TestExecutionListener の作者はリスナーの設定を 設定パラメータ で指定できるようにするといいでしょう。 リスナーは testPlanExecutionStarted(TestPlan) メソッドや testPlanExecutionFinished(TestPlan) メソッドを実装すれば、TestPlan から設定パラメータを参照できます。 具体例は UniqueIdTrackingListener を参照してください。

6.1.9. テスト実行リスナーを不活性にする

テスト実行リスナーを 使わずに テストスイートを実行できるようになっていると役に立つ場合があります。 例えば、自作の TestExecutionListener が外部システムにテスト結果を送信するようになっているとしたら、デバッグ実行した結果 は送信したくないはずです。 そういうときは、設定パラメータの junit.platform.execution.listeners.deactivate へテスト実行リスナーの完全修飾クラス名を指定すると、そのテスト実行リスナーは不活性(未登録状態)になります。

不活性にできるのは /META-INF/services/org.junit.platform.launcher.TestExecutionListener に記述して ServiceLoader で登録したリスナーだけです。 つまり、LauncherDiscoveryRequest で明示的に指定したリスナーは不活性にできません。

また、テスト実行を開始する前にテスト実行リスナーを登録するようになっているので、junit.platform.execution.listeners.deactivate は JVM のシステムパラメータか、設定ファイル(設定パラメータの構成 を参照)でしか指定できません。 Launcher へ渡す LauncherDiscoveryRequest では指定できないのです。

パターンマッチ記法

パターンマッチ記法 を参照してください。

6.1.10. Launcher の設定

自動検出機能や登録機能より、テストエンジンやリスナーの登録を詳細に制御したいときは、LauncherConfig のインスタンスを作成して LauncherFactory に渡すようにします。 普通は ビルダー API で LauncherConfig のインスタンスを作成します。

LauncherConfig launcherConfig = LauncherConfig.builder()
    .enableTestEngineAutoRegistration(false)
    .enableLauncherSessionListenerAutoRegistration(false)
    .enableLauncherDiscoveryListenerAutoRegistration(false)
    .enablePostDiscoveryFilterAutoRegistration(false)
    .enableTestExecutionListenerAutoRegistration(false)
    .addTestEngines(new CustomTestEngine())
    .addLauncherSessionListeners(new CustomLauncherSessionListener())
    .addLauncherDiscoveryListeners(new CustomLauncherDiscoveryListener())
    .addPostDiscoveryFilters(new CustomPostDiscoveryFilter())
    .addTestExecutionListeners(new LegacyXmlReportGeneratingListener(reportsDir, out))
    .addTestExecutionListeners(new CustomTestExecutionListener())
    .build();

LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
    .selectors(selectPackage("com.example.mytests"))
    .build();

try (LauncherSession session = LauncherFactory.openSession(launcherConfig)) {
    session.getLauncher().execute(request);
}

6.2. JUnit Platform レポート機能

junit-platform-reporting アーティファクトには、テスト結果レポートを生成する TestExecutionListener の実装クラスが複数含まれています。 これらのリスナーを使用するのはたいていの場合 IDE やビルドツールです。 org.junit.platform.reporting.legacy.xml パッケージには、次のような実装クラスがあります。

  • LegacyXmlReportGeneratingListenerTestPlan のルートノードごとに XML 形式のテスト結果レポートを生成します。 生成するレポートは JUnit 4 のテスト結果レポート形式として事実上の標準になっている XML 形式のテスト結果レポートで、Ant により一般的になりました。 LegacyXmlReportGeneratingListenerコンソールランチャー から使うと便利です。

junit-platform-launcher モジュールにもテスト結果レポートの生成に使用できる TestExecutionListener の実装クラスが含まれています。 詳しくは リスナーを使用する を参照してください。

6.3. JUnit Platform テストスイートエンジン

JUnit Platform では、JUnit Platform に対応している あらゆる テスト実行エンジンで、宣言的なテストスイートの宣言と実行ができるようになっています。

6.3.1. 準備

junit-platform-suite-apijunit-platform-suite-engine に加えて、少なくとも1つ のテスト実行エンジンをクラスパスから参照できるようにしなければなりません。 具体的な情報(グループIDやアーティファクトIDやバージョンなど)は 依存ライブラリに関するメタデータ を参照してください。

必要な依存ライブラリ
  • junit-platform-suite-api テスト スコープ。テストスイートを宣言するアノテーションを含んでいます。

  • junit-platform-suite-engine テストランタイム スコープ。テストスイートを定義する TestEngine の実装クラスを含んでいます。

これらの依存ライブラリを集約した junit-platform-suite アーティファクトを テスト スコープで追加するだけでも大丈夫です。
推移的依存ライブラリ
  • junit-platform-suite-commons テスト スコープ

  • junit-platform-launcher テスト スコープ

  • junit-platform-engine テスト スコープ

  • junit-platform-commons テスト スコープ

  • opentest4j テスト スコープ

6.3.2. テストスイートを宣言するアノテーション @Suite の使用例

あるクラスを @Suite で修飾すると、JUnit Platform はテストスイートとして認識します。 次のコード例では選択式(selector)とフィルターアノテーションを組み合わせてテストスイートの項目を構成しています。

import org.junit.platform.suite.api.IncludeClassNamePatterns;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
import org.junit.platform.suite.api.SuiteDisplayName;

@Suite
@SuiteDisplayName("JUnit Platform Suite Demo")
@SelectPackages("example")
@IncludeClassNamePatterns(".*Tests")
class SuiteDemo {
}
高度な設定項目
テストスイートに組み込むテストを発見し、選択するための設定項目は大量に存在します。 利用できるアノテーションと設定項目の完全な一覧は org.junit.platform.suite.api パッケージの Javadoc を参照してください。

6.4. JUnit Platform 検査キット

junit-platform-testkit アーティファクトは、JUnit Platform でテスト計画を実行し、テスト結果と期待結果を検証するための機能を提供します。 ただし、JUnit Platform 1.4 から単独のテスト実行エンジン(テスト実行エンジン検査キット を参照)による実行しかできなくなりました。

6.4.1. テスト実行エンジン検査キット

org.junit.platform.testkit.engine パッケージは、JUnit Platform で実行した TestEngine のテスト結果を期待結果と検証するためのフルーエント API を提供します。 主なエントリポイントは EngineTestKit クラスの static メソッド engine()execute() です。 LauncherDiscoveryRequest を作成するフルーエント API の利点を活用して、柔軟に engine() を選択することをお勧めします。

Launcher API の LauncherDiscoveryRequestBuilderLauncherDiscoveryRequest を作成するなら、EngineTestKitexecute() メソッドを使用してください。

以降のコード例では次のテストクラスを使用します。

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import example.util.Calculator;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

@TestMethodOrder(OrderAnnotation.class)
public class ExampleTestCase {

    private final Calculator calculator = new Calculator();

    @Test
    @Disabled("for demonstration purposes")
    @Order(1)
    void skippedTest() {
        // skipped ...
    }

    @Test
    @Order(2)
    void succeedingTest() {
        assertEquals(42, calculator.multiply(6, 7));
    }

    @Test
    @Order(3)
    void abortedTest() {
        assumeTrue("abc".contains("Z"), "abc does not contain Z");
        // aborted ...
    }

    @Test
    @Order(4)
    void failingTest() {
        // The following throws an ArithmeticException: "/ by zero"
        calculator.divide(1, 0);
    }

}

説明を簡略化するため、以降のセクションではエンジン ID が "junit-jupiter" である JUnit 本体の JupiterTestEngnine を使用します。 独自の TestEngine を試したければ、エンジン ID を変更するか、EngineTestKit.engine(TestEngine) メソッドを使ってください。

6.4.2. 統計情報を確認する

検査キットで最もよく使われる機能の1つが、TestPlan を実行している間に発生したイベントの統計情報を確認する機能です。 次のコード例では、Junit Jupiter のテスト実行エンジンが処理した テストコンテナテスト に関する統計情報を確認しています。 利用できる統計情報について詳しくは EventStatistics の Javadoc を参照してください。

import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;

import example.ExampleTestCase;

import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;

class EngineTestKitStatisticsDemo {

    @Test
    void verifyJupiterContainerStats() {
        EngineTestKit
            .engine("junit-jupiter") (1)
            .selectors(selectClass(ExampleTestCase.class)) (2)
            .execute() (3)
            .containerEvents() (4)
            .assertStatistics(stats -> stats.started(2).succeeded(2)); (5)
    }

    @Test
    void verifyJupiterTestStats() {
        EngineTestKit
            .engine("junit-jupiter") (1)
            .selectors(selectClass(ExampleTestCase.class)) (2)
            .execute() (3)
            .testEvents() (6)
            .assertStatistics(stats ->
                stats.skipped(1).started(3).succeeded(1).aborted(1).failed(1)); (7)
    }

}
1 テスト実行エンジンを指定する
2 テストクラスとして ExampleTestCase を指定する
3 TestPlan を実行する
4 テストコンテナ のイベントを選択する
5 テストコンテナ のイベントに関する統計情報を確認する
6 テスト のイベントを選択する
7 テスト のイベントに関する統計情報を確認する
verifyJupiterContainerStats() では、JupiterTestEngineExampleTestCase の両方が認識しているテストコンテナについて、startedsucceeded イベントが 2 回発生していることを確認しています。

6.4.3. イベントを確認する

統計情報を確認する だけでは、テスト実行時に期待する振る舞いを検証するのに不足しているなら、記録された Event そのものを直接的に確認できます。

例えば、ExampleTestCaseskippedTest() をスキップした理由を確認するには、次のように記述します。

以降のコード例における assertThatEvents()AssertJ の提供する org.assertj.core.api.Assertions.assertThat(events.list()) に対するショートカットです。

使用している 条件 について詳しくは EventConditions の Javadoc を参照してください。

import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.test;

import example.ExampleTestCase;

import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.junit.platform.testkit.engine.Events;

class EngineTestKitSkippedMethodDemo {

    @Test
    void verifyJupiterMethodWasSkipped() {
        String methodName = "skippedTest";

        Events testEvents = EngineTestKit (5)
            .engine("junit-jupiter") (1)
            .selectors(selectMethod(ExampleTestCase.class, methodName)) (2)
            .execute() (3)
            .testEvents(); (4)

        testEvents.assertStatistics(stats -> stats.skipped(1)); (6)

        testEvents.assertThatEvents() (7)
            .haveExactly(1, event(test(methodName),
                skippedWithReason("for demonstration purposes")));
    }

}
1 テスト実行エンジンを指定する
2 テストクラス ExampleTestCaseskippedTest() を指定する
3 TestPlan を実行する
4 テスト のイベントを選択する
5 テスト のイベントをローカル変数に保存する
6 ついでにイベントの統計情報を確認する
7 記録した テスト イベントに、"for demonstration purposes" というメッセージを含むイベントが1つだけ存在し、skippedTest() メソッドをスキップしたことを確認する

failingTest() メソッドのスローした例外クラスを確認するときは、次のように記述します。

使用している 条件 について詳しくは EventConditionsTestExecutionResultConditions の Javadoc を参照してください。

import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
import static org.junit.platform.testkit.engine.EventConditions.test;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;

import example.ExampleTestCase;

import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;

class EngineTestKitFailedMethodDemo {

    @Test
    void verifyJupiterMethodFailed() {
        EngineTestKit.engine("junit-jupiter") (1)
            .selectors(selectClass(ExampleTestCase.class)) (2)
            .execute() (3)
            .testEvents() (4)
            .assertThatEvents().haveExactly(1, (5)
                event(test("failingTest"),
                    finishedWithFailure(
                        instanceOf(ArithmeticException.class), message("/ by zero"))));
    }

}
1 テスト実行エンジンを指定する
2 テストクラスとして ExampleTestCase を指定する
3 TestPlan を実行する
4 テスト のイベントを選択する
5 記録した テスト イベントに、"/ by zero" というメッセージを含む ArithmeticException をスローしたイベントが1つだけ存在し、failingTest() メソッドが失敗したことを確認する

普通なら不要ですが、TestPlan を実行している間に発生した 全ての イベントを確認しなければならない場合があるかもしれません。 次のコード例では EngineTestKit API の assertEventsMatchExactly() メソッドでそのようなことをしています。

assertEventsMatchExactly() はイベントが発生したのと同じ順番で条件と比較していきます。 ExampleTestCase@TestMethodOrder(OrderAnnotation.class) で修飾しているし、それぞれのテストメソッドは @Order(…​) で修飾しているので、常に同じ順番で実行されます。 したがって、verifyAllJupiterEvents() の結果は(順序の違いに)影響されません。

順序を無視して 部分的に 比較したければ、assertEventsMatchLooselyInOrder()assertEventsMatchLoosely() を使うといいでしょう。

import static org.junit.jupiter.api.condition.JRE.JAVA_18;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.container;
import static org.junit.platform.testkit.engine.EventConditions.engine;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully;
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.started;
import static org.junit.platform.testkit.engine.EventConditions.test;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;

import java.io.StringWriter;
import java.io.Writer;

import example.ExampleTestCase;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.opentest4j.TestAbortedException;

class EngineTestKitAllEventsDemo {

    @Test
    @DisabledOnJre(JAVA_18) // https://github.com/assertj/assertj-core/issues/2340
    void verifyAllJupiterEvents() {
        Writer writer = // create a java.io.Writer for debug output

        EngineTestKit.engine("junit-jupiter") (1)
            .selectors(selectClass(ExampleTestCase.class)) (2)
            .execute() (3)
            .allEvents() (4)
            .debug(writer) (5)
            .assertEventsMatchExactly( (6)
                event(engine(), started()),
                event(container(ExampleTestCase.class), started()),
                event(test("skippedTest"), skippedWithReason("for demonstration purposes")),
                event(test("succeedingTest"), started()),
                event(test("succeedingTest"), finishedSuccessfully()),
                event(test("abortedTest"), started()),
                event(test("abortedTest"),
                    abortedWithReason(instanceOf(TestAbortedException.class),
                        message(m -> m.contains("abc does not contain Z")))),
                event(test("failingTest"), started()),
                event(test("failingTest"), finishedWithFailure(
                    instanceOf(ArithmeticException.class), message("/ by zero"))),
                event(container(ExampleTestCase.class), finishedSuccessfully()),
                event(engine(), finishedSuccessfully()));
    }

}
1 テスト実行エンジンを指定する
2 テストクラスとして ExampleTestCase を指定する
3 TestPlan を実行する
4 テスト のイベントを選択する
5 デバッグ用に全てのイベントを writer へ出力する(System.outSystem.err のような OutputStream に出力することもできます)
6 テストエンジンの発生した 全ての イベントが想定した順序になっているか確認する

前のコード例に登場する debug() は次のような出力をするでしょう。

All Events:
    Event [type = STARTED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.082280Z, payload = null]
    Event [type = STARTED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.089339Z, payload = null]
    Event [type = SKIPPED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:skippedTest()], timestamp = 2018-12-14T12:45:14.094314Z, payload = 'for demonstration purposes']
    Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.095182Z, payload = null]
    Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.104922Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]]
    Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.106121Z, payload = null]
    Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.109956Z, payload = TestExecutionResult [status = ABORTED, throwable = org.opentest4j.TestAbortedException: Assumption failed: abc does not contain Z]]
    Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.110680Z, payload = null]
    Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.111217Z, payload = TestExecutionResult [status = FAILED, throwable = java.lang.ArithmeticException: / by zero]]
    Event [type = FINISHED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.113731Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]]
    Event [type = FINISHED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.113806Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]]

7. API の進化

JUnit 5 の主要な目標の1つとして、多数のプロジェクトで使われつつ継続的に JUnit を進化させるため、メンテナーにとっての利便性を向上することが挙げられます。 JUnit 4 では、拡張機能やツールの作者が利用できる部品は、最初から用意されていた内部部品に限られていたため、とても苦労させられただけでなく、やりたいことができない場合もありました。

JUnit 5 が全てのライフサイクルに関するインターフェイスやクラスやメソッドを公開しているのは、そういう理由です。

7.1. API のバージョンと状態

公開している全てのアーティファクトのバージョン番号の形式は <major>.<minor>.<patch> です。 インターフェイス、クラス、メソッド、アノテーションにも、@API Guardian@API で同じバージョン番号が指定されています。 status 属性には次のような値が指定されています。

状態

内容

INTERNAL

内部用。JUnit 以外で使用するべきではない要素です。予告せずに削除する場合があります。

DEPRECATED

廃止予定。将来のマイナーアップデートで削除する可能性があるため、使用しないほうがいい要素です。

EXPERIMENTAL

実験的。JUnit チームとしてフィードバックを募集している新しい実験的な機能に関連する要素です。 注意して使用してください。 将来的に MAINTAINEDSTABLE へ変更になる可能性があります。 将来のパッチアップデートで予告せずにで削除する可能性があります。

MAINTAINED

保守。後方互換性を保つため、少なくとも 次のマイナーアップデート(同じメジャーバージョン)では変更しない要素です。 将来的に削除する場合は、まず状態を DEPRECATED へ変更します。

STABLE

安定。後方互換性を保つため、同じメジャーバージョンの間は変更しない要素です。

インターフェイスやクラスが @API で修飾されているなら、可視性が public のメンバーについても同じ意味になります。 インターフェイスやクラスのメンバーには、インターフェイスやクラスより安定性の低い status を指定する場合があります。

7.2. 実験的な API

次の表は現在のバージョンで 実験的 @API(status = EXPERIMENTAL) になっている要素です。 注意して使用してください。

Package Name Type Name Since

org.junit.jupiter.api

ClassDescriptor (interface)

5.8

org.junit.jupiter.api

ClassOrderer (interface)

5.8

org.junit.jupiter.api

ClassOrdererContext (interface)

5.8

org.junit.jupiter.api

DisplayNameGenerator.IndicativeSentences (class)

5.7

org.junit.jupiter.api

IndicativeSentencesGeneration (annotation)

5.7

org.junit.jupiter.api

MethodOrderer.DisplayName (class)

5.7

org.junit.jupiter.api

MethodOrderer.MethodName (class)

5.7

org.junit.jupiter.api

Order (annotation)

5.4

org.junit.jupiter.api

TestClassOrder (annotation)

5.8

org.junit.jupiter.api.extension

DynamicTestInvocationContext (interface)

5.8

org.junit.jupiter.api.extension

InvocationInterceptor (interface)

5.5

org.junit.jupiter.api.extension

InvocationInterceptor.Invocation (interface)

5.5

org.junit.jupiter.api.extension

LifecycleMethodExecutionExceptionHandler (interface)

5.5

org.junit.jupiter.api.extension

ReflectiveInvocationContext (interface)

5.5

org.junit.jupiter.api.extension

TestInstantiationException (class)

5.3

org.junit.jupiter.api.extension.support

TypeBasedParameterResolver (class)

5.6

org.junit.jupiter.api.io

TempDir (annotation)

5.4

org.junit.jupiter.api.parallel

Execution (annotation)

5.3

org.junit.jupiter.api.parallel

ExecutionMode (enum)

5.3

org.junit.jupiter.api.parallel

Isolated (annotation)

5.7

org.junit.jupiter.api.parallel

ResourceAccessMode (enum)

5.3

org.junit.jupiter.api.parallel

ResourceLock (annotation)

5.3

org.junit.jupiter.api.parallel

ResourceLocks (annotation)

5.3

org.junit.jupiter.api.parallel

Resources (class)

5.3

org.junit.jupiter.params.converter

TypedArgumentConverter (class)

5.7

org.junit.platform.commons.support

SearchOption (enum)

1.8

org.junit.platform.console

ConsoleLauncherToolProvider (class)

1.6

org.junit.platform.engine

EngineDiscoveryListener (interface)

1.6

org.junit.platform.engine

SelectorResolutionResult (class)

1.6

org.junit.platform.engine.support.config

PrefixedConfigurationParameters (class)

1.3

org.junit.platform.engine.support.discovery

EngineDiscoveryRequestResolver (class)

1.5

org.junit.platform.engine.support.discovery

EngineDiscoveryRequestResolver.Builder (class)

1.5

org.junit.platform.engine.support.discovery

EngineDiscoveryRequestResolver.InitializationContext (interface)

1.5

org.junit.platform.engine.support.discovery

SelectorResolver (interface)

1.5

org.junit.platform.engine.support.discovery

SelectorResolver.Context (interface)

1.5

org.junit.platform.engine.support.discovery

SelectorResolver.Match (class)

1.5

org.junit.platform.engine.support.discovery

SelectorResolver.Resolution (class)

1.5

org.junit.platform.engine.support.hierarchical

DefaultParallelExecutionConfigurationStrategy (enum)

1.3

org.junit.platform.engine.support.hierarchical

ExclusiveResource (class)

1.3

org.junit.platform.engine.support.hierarchical

ForkJoinPoolHierarchicalTestExecutorService (class)

1.3

org.junit.platform.engine.support.hierarchical

HierarchicalTestExecutorService (interface)

1.3

org.junit.platform.engine.support.hierarchical

Node.ExecutionMode (enum)

1.3

org.junit.platform.engine.support.hierarchical

Node.Invocation (interface)

1.4

org.junit.platform.engine.support.hierarchical

ParallelExecutionConfiguration (interface)

1.3

org.junit.platform.engine.support.hierarchical

ParallelExecutionConfigurationStrategy (interface)

1.3

org.junit.platform.engine.support.hierarchical

ResourceLock (interface)

1.3

org.junit.platform.engine.support.hierarchical

SameThreadHierarchicalTestExecutorService (class)

1.3

org.junit.platform.jfr

FlightRecordingDiscoveryListener (class)

1.8

org.junit.platform.jfr

FlightRecordingExecutionListener (class)

1.8

org.junit.platform.launcher

EngineDiscoveryResult (class)

1.6

org.junit.platform.launcher

LauncherDiscoveryListener (interface)

1.6

org.junit.platform.launcher

LauncherSession (interface)

1.8

org.junit.platform.launcher

LauncherSessionListener (interface)

1.8

org.junit.platform.launcher.listeners

UniqueIdTrackingListener (class)

1.8

org.junit.platform.launcher.listeners.discovery

LauncherDiscoveryListeners (class)

1.6

org.junit.platform.suite.api

ConfigurationParameter (annotation)

1.8

org.junit.platform.suite.api

ConfigurationParameters (annotation)

1.8

org.junit.platform.suite.api

DisableParentConfigurationParameters (annotation)

1.8

org.junit.platform.suite.api

SelectClasspathResource (annotation)

1.8

org.junit.platform.suite.api

SelectClasspathResources (annotation)

1.8

org.junit.platform.suite.api

SelectDirectories (annotation)

1.8

org.junit.platform.suite.api

SelectFile (annotation)

1.8

org.junit.platform.suite.api

SelectFiles (annotation)

1.8

org.junit.platform.suite.api

SelectModules (annotation)

1.8

org.junit.platform.suite.api

SelectUris (annotation)

1.8

org.junit.platform.suite.api

Suite (annotation)

1.8

7.3. 廃止予定の API

次の表は現在のバージョンで 廃止予定 @API(status = DEPRECATED) になっている要素です。 次のリリースで削除される可能性があるため、できるだけ使用しないでください。

Package Name Type Name Since

org.junit.jupiter.api

MethodOrderer.Alphanumeric (class)

5.7

org.junit.platform.commons.util

BlacklistedExceptions (class)

1.7

org.junit.platform.commons.util

PreconditionViolationException (class)

1.5

org.junit.platform.engine.support.filter

ClasspathScanningSupport (class)

1.5

org.junit.platform.engine.support.hierarchical

SingleTestExecutor (class)

1.2

org.junit.platform.launcher.listeners

LegacyReportingUtils (class)

1.6

org.junit.platform.runner

JUnitPlatform (class)

1.8

7.4. ツール側の @API 対応

@API Guardian プロジェクトでは、@API で修飾した要素の供給者と消費者をツールから使用するための機能を提供する予定です。 例えば、ツールの使用している JUnit API の @API をチェックできるようになるかもしれません。

8. Contributors

Browse the current list of contributors directly on GitHub.

9. Release Notes

The release notes are available here.

10. 付録

10.1. 再現性のあるビルド

JUnit 5.7 から、Javadoc を含まない jar ファイルを 再現性のあるビルド として作成するようになりました。

ビルド時の条件(Java のバージョンなど)が同じなら、何回ビルドをしてもバイト単位で同じ結果を生成するということです。

Maven Central や Sonartype に公開しているアーティファクトと同じビルド時の条件を用意できるなら、誰でも自分の環境で完全に同じアーティファクトをビルドできるのです。 Maven リポジトリのアーティファクトがソースコードからビルドした内容であることを確認できます。

10.2. 依存ライブラリに関するメタデータ

最終的なリリースやマイルストーンに対応するアーティファクトは Maven Central へデプロイします。 スナップショットのアーティファクトは SonarType の スナップショットリポジトリ/org/junit へデプロイします。

10.2.1. JUnit Platform

  • Group ID: org.junit.platform

  • Version: 1.9.0-SNAPSHOT

  • Artifact IDs:

    junit-platform-commons

    Common APIs and support utilities for the JUnit Platform. Any API annotated with @API(status = INTERNAL) is intended solely for usage within the JUnit framework itself. Any usage of internal APIs by external parties is not supported!

    junit-platform-console

    Support for discovering and executing tests on the JUnit Platform from the console. See コンソールランチャー for details.

    junit-platform-console-standalone

    An executable JAR with all dependencies included is provided in Maven Central under the junit-platform-console-standalone directory. See コンソールランチャー for details.

    junit-platform-engine

    Public API for test engines. See テスト実行エンジンの登録 for details.

    junit-platform-jfr

    Provides a LauncherDiscoveryListener and TestExecutionListener for Java Flight Recorder events on the JUnit Platform. See Java Flight Recorder を使用する for details.

    junit-platform-launcher

    Public API for configuring and launching test plans — typically used by IDEs and build tools. See JUnit Platform Launcher API for details.

    junit-platform-reporting

    TestExecutionListener implementations that generate test reports — typically used by IDEs and build tools. See JUnit Platform レポート機能 for details.

    junit-platform-runner

    Runner for executing tests and test suites on the JUnit Platform in a JUnit 4 environment. See JUnit 4 で JUnit Platform を実行する for details.

    junit-platform-suite

    JUnit Platform Suite artifact that transitively pulls in dependencies on junit-platform-suite-api and junit-platform-suite-engine for simplified dependency management in build tools such as Gradle and Maven.

    junit-platform-suite-api

    Annotations for configuring test suites on the JUnit Platform. Supported by the JUnit Platform Suite Engine and the JUnitPlatform runner.

    junit-platform-suite-commons

    Common support utilities for executing test suites on the JUnit Platform.

    junit-platform-suite-engine

    Engine that executes test suites on the JUnit Platform; only required at runtime. See JUnit Platform Suite Engine for details.

    junit-platform-testkit

    Provides support for executing a test plan for a given TestEngine and then accessing the results via a fluent API to verify the expected results.

10.2.2. JUnit Jupiter

  • Group ID: org.junit.jupiter

  • Version: 5.9.0-SNAPSHOT

  • Artifact IDs:

    junit-jupiter

    JUnit Jupiter aggregator artifact that transitively pulls in dependencies on junit-jupiter-api, junit-jupiter-params, and junit-jupiter-engine for simplified dependency management in build tools such as Gradle and Maven.

    junit-jupiter-api

    JUnit Jupiter API for writing tests and extensions.

    junit-jupiter-engine

    JUnit Jupiter test engine implementation; only required at runtime.

    junit-jupiter-params

    Support for parameterized tests in JUnit Jupiter.

    junit-jupiter-migrationsupport

    Support for migrating from JUnit 4 to JUnit Jupiter; only required for support for JUnit 4’s @Ignore annotation and for running selected JUnit 4 rules.

10.2.3. JUnit Vintage

  • Group ID: org.junit.vintage

  • Version: 5.9.0-SNAPSHOT

  • Artifact ID:

    junit-vintage-engine

    JUnit Vintage test engine implementation that allows one to run vintage JUnit tests on the JUnit Platform. Vintage tests include those written using JUnit 3 or JUnit 4 APIs or tests written using testing frameworks built on those APIs.

10.2.4. 部品表(BOM:Bill of Materials)

MavenGradle で 複数のアーティファクトを依存ライブラリとして簡単に管理できるよう、Maven BOM を公開しています。

  • Group ID: org.junit

  • Artifact ID: junit-bom

  • Version: 5.9.0-SNAPSHOT

10.2.5. Dependencies

ほとんどのアーティファクトは公開している Maven POM の依存関係として {API_Gurdian} のライブラリを記述しています。

  • Group ID: org.apiguardian

  • Artifact ID: apiguardian-api

  • Version: 1.1.2

また、ほとんどのアーティファクトは直接的に、あるいは、推移的に、OpenTest4J のライブラリへ依存しています。

  • Group ID: org.opentest4j

  • Artifact ID: opentest4j

  • Version: 1.2.0

10.3. Dependency Diagram

component diagram