1. 概要

この文書は、テストを書くプログラマーや拡張機能の作者、ビルドツールやIDEを含むテストエンジンの作者に対して、 包括的なリファレンスを提供することを目的としています。

Translations

『JUnit 5 User Guide』の v5.3.0 時点の日本語訳です。最新の情報は、 公式サイト を参照してください。 誤訳や誤植などのフィードバックは、 GitHub または @oohira までお願いします。

1.1. JUnit 5とは

これまでのバージョンのJUnitとは異なり、JUnit 5は 3つのサブプロジェクトに含まれる複数のモジュールで構成されます。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform は、JVM上で テストフレームワークを起動する ための基盤となり、このプラットフォーム上で動作するテストフレームワークを開発するための TestEngine APIを定義しています。加えて、コマンドラインからプラットフォームを起動するための Console Launcher や、 GradleMaven 用のビルドプラグイン、 JUnit 4ベースのテストランナー などを提供し、あらゆる TestEngine を実行できるようになっています。

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

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

1.2. サポートされるJavaバージョン

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

1.3. 助けてもらうには

Stack Overflow で質問するか、 Gitter で私たちにチャットしてください。

2. インストール

最終リリースや各マイルストーンでの成果物は、Maven Centralリポジトリにデプロイされています。

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

2.1. 依存関係メタデータ

2.1.1. JUnit Platform

  • Group ID: org.junit.platform

  • Version: 1.3.0

  • Artifact IDs:

    junit-platform-commons

    JUnitの内部的な共通ライブラリ/ユーティリティです。これらのユーティリティは、 JUnitフレームワーク自身で使われることだけを想定しています。 外部のプログラムからの利用はサポートされていません。 利用は自己責任で!

    junit-platform-console

    コンソールからJUnit Platform上でテストを見つけて実行する機能を提供します。 詳細は、 Console Launcher を参照してください。

    junit-platform-console-standalone

    すべての依存関係を含む実行可能なJARは、Maven Centralリポジトリの junit-platform-console-standalone ディレクトリで提供されています。詳細は、 Console Launcher を参照してください。

    junit-platform-engine

    テストエンジンの公開APIです。詳細は、 Plugging in your own Test Engine を参照してください。

    junit-platform-launcher

    テストプランを構成して起動するための公開APIです。通常は、IDEやビルドツールに使われます。 詳細は、 JUnit Platform Launcher API を参照してください。

    junit-platform-runner

    JUnit Platform上のテストおよびテストスイートをJUnit 4環境で実行するテストランナーです。 詳細は、 JUnit Platformの実行にJUnit 4を使う を参照してください。

    junit-platform-suite-api

    JUnit Platform上でテストスイートを構成するためのアノテーションです。 JUnitPlatformランナー と(おそらく)サードパーティーの TestEngine 実装にもサポートされます。

    junit-platform-surefire-provider

    Maven Surefire を使って、 JUnit Platform上でテストを見つけて実行する機能を提供します。

2.1.2. JUnit Jupiter

  • Group ID: org.junit.jupiter

  • Version: 5.3.0

  • Artifact IDs:

    junit-jupiter-api

    JUnit Jupiterで テスト拡張機能 を書くためのAPIです。

    junit-jupiter-engine

    JUnit Jupiterのテストエンジン実装です。実行時だけ必要になります。

    junit-jupiter-params

    JUnit Jupiterで パラメーター化テスト をサポートします。

    junit-jupiter-migrationsupport

    JUnit 4からJUnit Jupiterへの移行をサポートします。 JUnit Jupiter上で、JUnit 4ルールを実行したいときだけ必要になります。

2.1.3. JUnit Vintage

  • Group ID: org.junit.vintage

  • Version: 5.3.0

  • Artifact ID:

    junit-vintage-engine

    JUnit Vintageのテストエンジン実装です。JUnit 3またはJUnit 4で書かれた昔のテストを 新しいJUnit Platform上で実行できるようにします。

2.1.4. Bill of Materials (BOM)

Bill of Materials POM を使うと、 Maven または Gradle で前述のライブラリを複数参照するときに依存関係の管理を容易にすることができます。

  • Group ID: org.junit

  • Artifact ID: junit-bom

  • Version: 5.3.0

2.1.5. 依存関係

前述のすべてのJARが次の @API Guardian JARに依存しています。

  • Group ID: org.apiguardian

  • Artifact ID: apiguardian-api

  • Version: 1.0.0

加えて、ほとんどのJARが次の OpenTest4J JARに直接または間接的に依存しています。

  • Group ID: org.opentest4j

  • Artifact ID: opentest4j

  • Version: 1.1.0

2.2. 依存関係図

component diagram

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

junit5-samples リポジトリは、 JUnit JupiterベースとJUnit Vintageベースのプロジェクトの様々なサンプルを提供します。 以下のプロジェクトでビルドスクリプト(例えば、build.gradlepom.xml など) を見つけられるでしょう。

3. テストを書く

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

import org.junit.jupiter.api.Test;

class FirstJUnit5Tests {

    @Test
    void myFirstTest() {
        assertEquals(2, 1 + 1);
    }

}

3.1. アノテーション

JUnit Jupiterは、テストを構成し、フレームワークを拡張するために、次のアノテーションをサポートします。

中心となるすべてのアノテーションは、 junit-jupiter-api モジュールの org.junit.jupiter.api パッケージに含まれます。

アノテーション 説明

@Test

このメソッドが、テストメソッドであることを示します。JUnit 4の @Test アノテーションとは異なり、このアノテーションはどんな属性も宣言しません。というのも、JUnit Jupiterのテスト拡張は、それ専用のアノテーションベースで行われるからです。このメソッドは、オーバーライド されない限り、継承 されます。

@ParameterizedTest

このメソッドが、パラメーター化テスト であることを示します。このメソッドは、オーバーライド されない限り、継承 されます。

@RepeatedTest

このメソッドが、繰り返しテスト のためのテストテンプレートであることを示します。このメソッドは、オーバーライド されない限り、継承 されます。

@TestFactory

このメソッドが、動的テスト のためのテストファクトリであることを示します。このメソッドは、オーバーライド されない限り、継承 されます。

@TestInstance

アノテーションを付与したテストクラスに対して、テストインスタンスのライフサイクル を設定するために使われます。このアノテーションは、継承 されます。

@TestTemplate

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

@DisplayName

テストクラスやテストメソッドにカスタムの表示名を指定します。このアノテーションは、継承 されません。

@BeforeEach

このメソッドが、現在のクラスの @Test, @RepeatedTest, @ParameterizedTest, @TestFactory メソッドの 前に毎回 実行されるよう指定します。JUnit 4の @Before と同じです。このメソッドは、オーバーライド されない限り、継承 されます。

@AfterEach

このメソッドが、現在のクラスの @Test, @RepeatedTest, @ParameterizedTest, @TestFactory メソッドの 後に毎回 実行されるよう指定します。JUnit 4の @After と同じです。このメソッドは、オーバーライド されない限り、継承 されます。

@BeforeAll

このメソッドが、現在のクラスの すべての @Test, @RepeatedTest, @ParameterizedTest, @TestFactory メソッドの 前に一度だけ 実行されるよう指定します。JUnit 4の @BeforeClass と同じです。 このメソッドは、(隠すオーバーライドする かしない限り)継承 されますが、(テストインスタンスのライフサイクル に "per-class" が使われない限り)static でなければいけません。

@AfterAll

このメソッドが、現在のクラスの すべての @Test, @RepeatedTest, @ParameterizedTest, @TestFactory メソッドの 後に一度だけ 実行されるよう指定します。JUnit 4の @AfterClass と同じです。このメソッドは、(隠すオーバーライドする かしない限り)継承 されますが、(テストインスタンスのライフサイクル に "per-class" が使われない限り)static でなければいけません。

@Nested

このクラスが、ネストした非staticのテストクラスであることを示します。 @BeforeAll および @AfterAll メソッドは、テストインスタンスのライフサイクル に "per-class" が使われない限り、@Nested テストクラスの中で直接使うことはできません。このアノテーションは、継承 されません。

@Tag

クラスまたはメソッドレベルでテストをフィルタリングするための タグ を宣言するのに使われます。TestNGのテストグループやJUnit 4のCategoryと同じです。このアノテーションは、クラスレベルでは 継承 されますが、メソッドレベルでは 継承 されません。

@Disabled

テストクラスやテストメソッドを 無効化 するのに使われます。JUnit 4の @Ignore と同じです。このアノテーションは、継承 されません。

@ExtendWith

カスタムの 拡張機能 を登録するために使われます。このアノテーションは、継承 されます。

@Test, @TestTemplate, @RepeatedTest, @BeforeAll, @AfterAll, @BeforeEach, @AfterEach アノテーションの付与されたメソッドは、戻り値を返してはいけません。

いくつかのアノテーションは現在 実験中 です。詳細は、 実験的なAPI のテーブルを参照してください。

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

JUnit Jupiterアノテーションは、メタアノテーション として使うことができます。つまり、 メタアノテーションの意味を自動的に 継承 した独自の 合成アノテーション を作れるということです。

例えば、@Tag("fast") をコードベース全体にコピー&ペーストする代わりに (タグとフィルタリング 参照)、以下の @Fast のような名前のカスタム 合成アノテーション を作ることができます。 @Fast は、@Tag("fast") の代わりに使うことができます。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.Tag;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface Fast {
}

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

テストメソッド (test method) とは、@Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @TestTemplate のいずれかが直接またはメタアノテーションとして付与された インスタンスメソッドです。テストクラス (test class) とは、テストメソッドを少なくとも1つもつ トップレベルまたはstaticな内部クラスです。

標準的なテストクラス
import static org.junit.jupiter.api.Assertions.fail;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class StandardTests {

    @BeforeAll
    static void initAll() {
    }

    @BeforeEach
    void init() {
    }

    @Test
    void succeedingTest() {
    }

    @Test
    void failingTest() {
        fail("失敗するテスト");
    }

    @Test
    @Disabled("デモ用")
    void skippedTest() {
        // 実行されない
    }

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}
テストクラスやテストメソッドは、 public である必要がありません。

3.3. 表示名

テストクラスやテストメソッドは、カスタムの表示名を宣言できます。 空白や特殊文字だけでなく絵文字も含めることができ、テストランナーやテストレポートに表示されます。

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

@DisplayName("特殊なテストケース")
class DisplayNameDemo {

    @Test
    @DisplayName("スペースを 含む カスタムの テスト名")
    void testWithDisplayNameContainingSpaces() {
    }

    @Test
    @DisplayName("╯°□°)╯")
    void testWithDisplayNameContainingSpecialCharacters() {
    }

    @Test
    @DisplayName("😱")
    void testWithDisplayNameContainingEmoji() {
    }

}

3.4. アサーション

JUnit Jupiterは、JUnit 4にあった多くのアサーションメソッドをもち、 Java 8のラムダ式と一緒に使いやすいものもいくつか追加しています。 すべてのJUnit Jupiterアサーションは、 org.junit.jupiter.api.Assertions クラスの static メソッドです。

import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

class AssertionsDemo {

    @Test
    void standardAssertions() {
        assertEquals(2, 2);
        assertEquals(4, 4, "省略可能なアサーションメッセージは最後のパラメーター");
        assertTrue('a' < 'b', () -> "アサーションメッセージは遅延評価できる -- "
                + "不必要に複雑なメッセージを構築するコストを割けるために");
    }

    @Test
    void groupedAssertions() {
        // アサーションをグループ化すると、すべてのアサーションが一度に実行され、
        // すべての失敗がまとめて報告される。
        assertAll("person",
            () -> assertEquals("John", person.getFirstName()),
            () -> assertEquals("Doe", person.getLastName())
        );
    }

    @Test
    void dependentAssertions() {
        // コードブロック内でアサーションが失敗すると、同じブロック内の後続のコードはスキップされる。
        assertAll("properties",
            () -> {
                String firstName = person.getFirstName();
                assertNotNull(firstName);

                // 上のアサーションが成功した場合のみ実行される。
                assertAll("first name",
                    () -> assertTrue(firstName.startsWith("J")),
                    () -> assertTrue(firstName.endsWith("n"))
                );
            },
            () -> {
                // グループ化されたアサーションは、first name のアサーションとは独立して実行される。
                String lastName = person.getLastName();
                assertNotNull(lastName);

                // 上のアサーションが成功した場合のみ実行される。
                assertAll("last name",
                    () -> assertTrue(lastName.startsWith("D")),
                    () -> assertTrue(lastName.endsWith("e"))
                );
            }
        );
    }

    @Test
    void exceptionTesting() {
        Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
            throw new IllegalArgumentException("a message");
        });
        assertEquals("a message", exception.getMessage());
    }

    @Test
    void timeoutNotExceeded() {
        // 次のアサーションは成功する。
        assertTimeout(ofMinutes(2), () -> {
            // 2分未満で終わるタスクを実行する。
        });
    }

    @Test
    void timeoutNotExceededWithResult() {
        // 次のアサーションは成功し、指定されたオブジェクトを返す。
        String actualResult = assertTimeout(ofMinutes(2), () -> {
            return "a result";
        });
        assertEquals("a result", actualResult);
    }

    @Test
    void timeoutNotExceededWithMethod() {
        // 次のアサーションは、メソッド参照を実行してオブジェクトを返す。
        String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting);
        assertEquals("Hello, World!", actualGreeting);
    }

    @Test
    void timeoutExceeded() {
        // 次のアサーションは、以下のようなエラーメッセージを出して失敗する:
        // execution exceeded timeout of 10 ms by 91 ms
        assertTimeout(ofMillis(10), () -> {
            // 10ミリ秒より時間のかかるタスクをシミュレートする。
            Thread.sleep(100);
        });
    }

    @Test
    void timeoutExceededWithPreemptiveTermination() {
        // 次のアサーションは、以下のようなエラーメッセージを出して失敗する:
        // execution timed out after 10 ms
        assertTimeoutPreemptively(ofMillis(10), () -> {
            // 10ミリ秒より時間のかかるタスクをシミュレートする。
            Thread.sleep(100);
        });
    }

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

}

JUnit Jupiterは、 Kotlin と一緒に使いやすいアサーションもいくつか含んでいます。すべてのJUnit Jupiter Kotlinアサーションは、 org.junit.jupiter.api パッケージのトップレベル関数です。

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

class AssertionsKotlinDemo {

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

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

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

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

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

多くのテストシナリオではJUnit Jupiterが提供するアサーション機能だけで十分ですが、 時にはさらなるパワーや マッチャー のような追加機能が必要になることもあります。 そのような場合には、 AssertJHamcrestTruth のようなサードパーティーの アサーションライブラリを使うことをJUnitチームはお勧めします。 開発者は、好みに応じてアサーションライブラリを自由に選択することができます。

例えば、 マッチャー と流れるようなAPIを組み合わせれば、アサーションをもっと説明的で 読みやすくすることができます。しかし、JUnit Jupiterの org.junit.jupiter.api.Assertions クラスは、 JUnit 4の org.junit.Assert クラスのようにHamcrestの Matcher を受け付ける assertThat() メソッドを提供していません。代わりに、開発者はサードパーティーのアサーションライブラリが 提供するマッチャーを利用することが推奨されます。

次の例は、JUnit JupiterテストでHamcrestの assertThat() を使う方法を示しています。 Hamcrestライブラリをクラスパスに追加しさえすれば、 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 org.junit.jupiter.api.Test;

class HamcrestAssertionDemo {

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

}

当然ながら、JUnit 4のプログラミングモデルに基づくレガシーなテストでは、引き続き org.junit.Assert#assertThat を使い続けることができます。

3.5. 前提条件

JUnit Jupiterは、JUnit 4が提供する前提条件用メソッドの一部を備え、 Java 8のラムダ式と一緒に使いやすいものをいくつか追加しています。 JUnit Jupiterの前提条件メソッドは、すべて org.junit.jupiter.api.Assumptions クラスのstaticメソッドです。

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumingThat;

import org.junit.jupiter.api.Test;

class AssumptionsDemo {

    @Test
    void testOnlyOnCiServer() {
        assumeTrue("CI".equals(System.getenv("ENV")));
        // 残りのテスト
    }

    @Test
    void testOnlyOnDeveloperWorkstation() {
        assumeTrue("DEV".equals(System.getenv("ENV")),
            () -> "テストを中断: 開発者マシンではない");
        // 残りのテスト
    }

    @Test
    void testInAllEnvironments() {
        assumingThat("CI".equals(System.getenv("ENV")),
            () -> {
                // これらのアサーションはCIサーバーでのみ実行する
                assertEquals(2, 2);
            });

        // これらのアサーションはすべての環境で実行する
        assertEquals("a string", "a string");
    }

}

3.6. テストの無効化

テストクラス全体または個々のテストメソッドは、 @Disabled アノテーションや 条件付きテスト実行 で説明するアノテーション、 あるいはカスタムの ExecutionCondition によって 無効化 することができます。

次の例は、@Disabled されたテストクラスです。

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

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

そして次の例は、@Disabled されたテストメソッドをもつテストクラスです。

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

class DisabledTestsDemo {

    @Disabled
    @Test
    void testWillBeSkipped() {
    }

    @Test
    void testWillBeExecuted() {
    }
}

3.7. 条件付きテスト実行

JUnit Jupiterの ExecutionCondition 拡張APIは、 開発者がある一定の条件にもとづいて 手続き的に コンテナやテストを有効化したり、 無効化したりすることができるようにします。 簡単な例は、@Disabled アノテーションをサポートする DisabledCondition です (テストの無効化 参照)。

JUnit Jupiterは、org.junit.jupiter.api.condition パッケージで @Disabled 以外にもアノテーションベースの条件をサポートしています。これにより、開発者は 宣言的に コンテナやテストを有効化したり、無効化したりすることができます。 詳細は、以降の節を参照してください。

合成アノテーション

以降で説明するいずれの 条件 アノテーションも、カスタムの 合成アノテーション を作るためのメタアノテーションとして使われうる点に注意してください。例えば、 @EnabledOnOsデモ@TestOnMac アノテーションは、@Test@EnabledOnOs を1つの再利用可能なアノテーションとして 組み合わせる方法を示しています。

以降の節で取り上げられる 条件 アノテーションは、あるテストインターフェースやテストクラス、 テストメソッドに対して一度しか宣言できません。条件アノテーションがある要素に対して直接、 間接、あるいはメタアノテーションで複数回指定されたとしても、JUnitが最初に見つけた アノテーションだけが使われます。その他のアノテーションは、黙って無視されます。ただし、 org.junit.jupiter.api.condition パッケージのそれぞれの条件アノテーションは、 組み合わせて使うことができる点に注意してください。

3.7.1. オペレーティングシステム条件

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

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

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

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

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

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

3.7.2. Javaランタイム環境条件

@EnabledOnJre および @DisabledOnJre アノテーションを使うと、 コンテナやテストを特定のバージョンのJavaランタイム環境(JRE)で有効にしたり無効にしたりできます。

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

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

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

3.7.3. システムプロパティ条件

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

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

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

3.7.4. 環境変数条件

@EnabledIfEnvironmentVariable および @DisabledIfEnvironmentVariable アノテーションを使うと、指定した名前をもつ環境変数の値に応じて、コンテナやテストを 有効にしたり無効にしたりできます。matches 属性で指定する値は、正規表現として解釈されます。

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

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

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

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

@EnabledIf および @DisabledIf を使った条件付きテスト実行は、 現在 試験的な 機能です。詳細は、実験的なAPI のテーブルを参考にしてください。
もしスクリプトのロジックが現在のオペレーティングシステムやJavaランタイム環境の バージョン、特定のJVMシステムプロパティや環境変数だけに依存するなら、その目的専用の 組み込みアノテーションの使用を検討すべきです。詳細は、この章の前の節を参照してください。
もし同じようなスクリプトベースの条件を何回も使っていると感じたら、 高速かつタイプセーフで、よりメンテナンスしやすい方法で実装するために、専用の ExecutionCondition 拡張を書くことを検討してください。
@Test // 静的なJavaScriptの式
@EnabledIf("2 * 3 == 6")
void willBeExecuted() {
    // ...
}

@RepeatedTest(10) // 動的なJavaScriptの式
@DisabledIf("Math.random() < 0.214159")
void mightNotBeExecuted() {
    // ...
}

@Test // システムプロパティをテストする正規表現
@DisabledIf("/32/.test(systemProperty.get('os.arch'))")
void disabledOn32BitArchitectures() {
    assertFalse(System.getProperty("os.arch").contains("32"));
}

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

@Test // 複数行のスクリプト、カスタムのエンジン名、カスタムの理由(訳注:テストが無効化された場合に表示される)
@EnabledIf(value = {
                "load('nashorn:mozilla_compat.js')",
                "importPackage(java.time)",
                "",
                "var today = LocalDate.now()",
                "var tomorrow = today.plusDays(1)",
                "tomorrow.isAfter(today)"
            },
            engine = "nashorn",
            reason = "Self-fulfilling: {result}")
void theDayAfterTomorrow() {
    LocalDate today = LocalDate.now();
    LocalDate tomorrow = today.plusDays(1);
    assertTrue(tomorrow.isAfter(today));
}
スクリプトバインディング

以下の名前は、スクリプトのコンテキストに束縛されるため、スクリプト内で使うことができます。 accessor は、単純な String get(String name) メソッドを介したマップ構造への アクセスを提供します。

Name Type Description

systemEnvironment

accessor

オペレーティングシステムの環境変数へのアクセッサ

systemProperty

accessor

JVMのシステムプロパティへのアクセッサ

junitConfigurationParameter

accessor

設定パラメーターへのアクセッサ

junitDisplayName

String

テストやコンテナの表示名

junitTags

Set<String>

テストやコンテナにアサインされたすべてのタグ

junitUniqueId

String

テストやコンテナの一意なID

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

テストクラスとテストメソッドには、@Tag アノテーションでタグを付与することができます。 これらのタグはあとで テストを発見・実行 するときのフィルタリングに 使うことができます。

3.8.1. タグの構文規則

  • タグは、null または であってはならない

  • トリミングした タグは、空白文字を含んではならない

  • トリミングした タグは、ISO制御文字を含んではならない

  • トリミングした タグは、次の 予約済み文字 を含んではならない

    • ,: カンマ

    • (: 左括弧

    • ): 右括弧

    • &: アンパサンド

    • |: 縦棒

    • !: 感嘆符

上のコンテキストで "トリミングした" とは、先頭および末尾の空白文字が 取り除かれていることを意味する。
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

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

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

}

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

個々のテストメソッドを隔離された環境で実行し、 テストインスタンスをミュータブルにすると起こりうる意図しない副作用を避けるため、 JUnitは テストメソッドテストクラスとテストメソッド 参照) 毎にそのテストクラスの新しいインスタンスを生成して実行します。 この "per-method(メソッド毎)" のテストインスタンスライフサイクルは、JUnit Jupiter のデフォルトの挙動で、過去のJUnitのバージョンと同じです。

"per-method" のテストインスタンスライフサイクルモードが有効であったとしても、 条件 (例えば、@Disabled@DisabledOnOs など)によって テストメソッド無効化 されている場合は、 テストクラスのインスタンスが作り直されない点に注意してください。

もしすべてのテストメソッドを同じテストインスタンスで実行させたい場合は、テストクラスに @TestInstance(Lifecycle.PER_CLASS) アノテーションを付与してください。 このモードを使うと、新しいテストインスタンスはテストクラス毎に1回だけ生成されます。 そのため、もしテストメソッドがインスタンス変数の状態に依存するのであれば、 @BeforeEach@AfterEach メソッドで状態をリセットする必要があります。

"per-class(クラス毎)" モードは、デフォルトの "per-method(メソッド毎)" モードに比べて 追加のメリットがあります。具体的には、"per-class" モードでは @BeforeAll および @AfterAll をインターフェースの default メソッドやクラスの非staticなメソッドに 対して宣言できるようになります。そのため、"per-class" モードでは @BeforeAll@AfterAll メソッドを @Nested テストクラスでも使えます。

もしプログラミング言語Kotlinでテストを書く場合は、テストインスタンスのライフサイクルを "per-class" モードに切り替えると、@BeforeAll および @AfterAll メソッドを より実装しやすいでしょう。

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

テストクラスやテストインターフェースに @TestInstance アノテーションを付与すると、 JUnit Jupiterは デフォルト のライフサイクルモードを使います。標準の デフォルト モードは PER_METHOD ですが、テストプラン全体で デフォルト を変更することもできます。 テストインスタンスのデフォルトのライフサイクルモードを変更するには、単に junit.jupiter.testinstance.lifecycle.default 設定パラメーターTestInstance.Lifecycle で定義された列挙定数を指定するだけです。 これは、JVMシステムプロパティや Launcher に渡される LauncherDiscoveryRequest設定パラメーター 、あるいはJUnit Platform設定ファイルなどで指定できます (詳細は、設定パラメーター 参照)。

例えば、デフォルトのライフサイクルモードを Lifecycle.PER_CLASS に設定するには、 次のシステムプロパティを指定してJVMを起動します。

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

ただし、デフォルトのライフサイクルモードをJUnit Platform設定ファイルで指定する方が、 よりロバストである点に注意してください。というのも、設定ファイルならプロジェクトの バージョン管理システムにチェックインでき、IDEやビルドツールからも使えるからです。

JUnit Platform設定ファイルを使ってデフォルトのライフサイクルモードを Lifecycle.PER_CLASS にするには、クラスパスのルート(例えば、src/test/resources)に junit-platform.properties という名前で次のファイルを作ります。

junit.jupiter.testinstance.lifecycle.default = per_class

テストインスタンスの デフォルト のライフサイクルモードを変更する場合は、 一貫性のある方法で適用しないと予測できない結果や不安定なビルドにつながる可能性があります。 例えば、ビルドでは "per-class" をデフォルトとして設定していても、IDEが "per-method" でテストを実行していると、ビルドサーバーで起きたエラーをデバッグするのが難しくなります。 そのため、JVMシステムプロパティの代わりにJUnit Platform設定ファイルを使って デフォルトを変更することをお勧めします。

3.10. ネストしたテスト

ネストしたテストは、テスト作成者がテストのグループ関係を表現しやすくします。 詳しい例は次の通りです。

スタックをテストするためのネストしたテストスイート
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.EmptyStackException;
import java.util.Stack;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

@DisplayName("A stack")
class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, () -> stack.pop());
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, () -> stack.peek());
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {

            String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}
非staticなネストクラス(すなわち、内部クラス)だけが、@Nested テストクラスとして使えます。ネストの深さは自由で、すべての内部クラスがテストクラスの 一部とみなされます。ただし、1つだけ例外があり、@BeforeAll および @AfterAll メソッドは、デフォルトでは 機能しません。というのも、Javaでは内部クラスで static メンバーを使用できないからです。しかし、この制限は @Nested テストクラスに @TestInstance(Lifecycle.PER_CLASS) アノテーションを付与することで回避できます ( テストインスタンスのライフサイクル 参照)。

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

これまでのバージョンのJUnitでは、テストクラスのコンストラクタとメソッドは (少なくとも標準の Runner 実装では)引数をもつことができませんでした。 JUnit Jupiterの大きな変更点の1つは、テストのコンストラクタやメソッドが 引数をもてるようになったことです。これは大きな柔軟性をもたらし、 依存性注入 を可能にします。

ParameterResolver は、実行時に 動的に 引数を解決したいテスト拡張のための APIを定義しています。もしテストコンストラクタや @Test, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll メソッドが引数をもつ場合、 その引数は登録済みの ParameterResolver によって解決されます。

現状、自動的に登録される組み込みのResolverが3つあります。

  • TestInfoParameterResolver: もしメソッドの引数の型が TestInfo の場合、 TestInfoParameterResolver が現在のテストに応じた TestInfo をその引数の値として 与えます。 TestInfo は、テストの表示名やテストクラス、テストメソッド、付与されたタグなど、 現在のテストに関する情報を取得するために使用できます。表示名は、テストクラスやテストメソッド の名前か、@DisplayName で設定されたカスタムの名前か、どちらかになります。

    TestInfo は、JUnit 4の TestName ルールの代替として使えます。 次の例は、TestInfo をテストのコンストラクタ、@BeforeEach メソッド、および @Test メソッドに注入する方法を示しています。

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 の現在の繰り返し回数や合計回数の情報を 取得するために使用できます。ただし、RepetitionInfoParameterResolver@RepeatedTest 以外では登録されないことに注意してください。 繰り返しテストの例 を参照。

  • TestReporterParameterResolver: もしメソッドの引数の型が TestReporter の場合、TestReporterParameterResolverTestReporter インスタンスを与えます。 TestReporter は、現在のテスト実行に関する追加のデータを出力するために使用できます。 このデータは、TestExecutionListener.reportingEntryPublished() を通して取得できるので、IDEで参照したり、レポートに含めたりできます。

    JUnit Jupiterでは、JUnit 4で stdoutstderr に情報を出力していた代わりに、 TestReporter を使うべきです。 @RunWith(JUnitPlatform.class) を使うときも、 すべてのレポート情報が stdout に出力されます。

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) {
        testReporter.publishEntry(
            Map.of(
                "user name", "dk38",
                "award year", "1974"
            ));
    }

}
そのほかの ParameterResolver は、@ExtendWith を使って適切な 拡張機能 を登録することで、明示的に有効にしなければなりません。

カスタムの ParameterResolver のサンプルとしては、RandomParametersExtension を参照してください。本番利用を想定したものではありませんが、 拡張モデルとパラメーター解決処理のシンプルさや高い表現力を示しています。 MyRandomParametersTest は、乱数を @Test メソッドに注入する例になっています。

@ExtendWith(RandomParametersExtension.class)
class MyRandomParametersTest {

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

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

}

実世界のユースケースとしては、MockitoExtensionSpringExtension のソースコードを確認してください。

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

JUnit Jupiterでは、@Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @TestTemplate, @BeforeEach, @AfterEach アノテーションをインターフェースの default メソッドに対して宣言することもできます。 @BeforeAll@AfterAll はテストインターフェースの static メソッドにしか宣言できませんが、 @TestInstance(Lifecycle.PER_CLASS) がテストインターフェースまたはテストクラスに 付与されていればインターフェースの default メソッドに宣言することもできます (テストインスタンスのライフサイクル 参照)。以下はサンプルです。

@TestInstance(Lifecycle.PER_CLASS)
interface TestLifecycleLogger {

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

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

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

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

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

}
interface TestInterfaceDynamicTestsDemo {

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

}

@ExtendWith@Tag はテストインターフェースに宣言することで、 そのインタフェースを実装したクラスにタグと拡張機能を自動的に継承させることができます。 テスト実行前後のコールバック にある TimingExtension のソースコードを参照してください。

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

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

class TestInterfaceDemo implements TestLifecycleLogger,
        TimeExecutionLogger, TestInterfaceDynamicTestsDemo {

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

}

TestInterfaceDemo を実行すると、次のような出力となります。

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

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

BUILD SUCCESSFUL

この機能の別の適用例としては、インタフェース契約に対するテストが考えられます。 例えば、Object.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 "foo";
    }

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

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

}
前述のテストはサンプル目的であり、完全なものではありません。

3.13. 繰り返しテスト

JUnit Jupiterは、@RepeatedTest アノテーションを使って指定した回数だけ 繰り返しテストを実行する機能を提供しています。 繰り返しテストの1回毎の呼び出しは、ライフサイクルコールバックや拡張機能など、通常の @Test メソッドの実行と同じように振る舞います。

次の例は、自動的に10回繰り返す repeatedTest() を宣言する方法を示しています。

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

繰り返し回数を指定するだけでなく、@RepeatedTest アノテーションの name 属性を使って、繰り返し実行毎の表示名を変更することもできます。表示名は、 静的なテキストと動的なプレースホルダーを組み合わせたパターンにできます。 以下が、現在サポートされているプレースホルダーです。

  • {displayName}: @RepeatedTest メソッドの表示名

  • {currentRepetition}: 現在の実行回数

  • {totalRepetitions}: トータルの実行回数

デフォルトの表示名は、"repetition {currentRepetition} of {totalRepetitions}" というパターンに従います。そのため、前述の repeatedTest() に対する表示名は、 repetition 1 of 10, repetition 2 of 10 のようになります。 もし @RepeatedTest メソッドの名前を含めたい場合は、カスタムのパターンを指定するか、 定義済みの RepeatedTest.LONG_DISPLAY_NAME パターンを指定できます。 後者は、"{displayName} :: repetition {currentRepetition} of {totalRepetitions}" というパターンと同等で、repeatedTest() :: repetition 1 of 10, repeatedTest() :: repetition 2 of 10 のようになります。

現在の繰り返し回数やトータルの繰り返し回数をプログラムから取得するためには、 RepetitionInfo インスタンスを @RepeatedTest, @BeforeEach, @AfterEach メソッドのいずれかに注入させることができます。

3.13.1. 繰り返しテストの例

この節の最後の RepeatedTestsDemo クラスは、繰り返しテストの様々な例を示しています。

repeatedTest() メソッドは、前の節で紹介した例と同じです。一方、 repeatedTestWithRepetitionInfo() は、トータルの繰り返し回数を取得するために RepetitionInfo インスタンスをテストに注入させる方法を示しています。

その次の2つのメソッドは、@RepeatedTest メソッドに対するカスタムの @DisplayName を各繰り返しの表示名に含める方法を示しています。customDisplayName() は、 カスタムの表示名とパターンを組み合わせて指定し、生成された表示名を検証するのに TestInfo を使っています。 {displayName}@DisplayName の宣言から Repeat! となり、 {currentRepetition}/{totalRepetitions}1/1 となります。 一方、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(testInfo.getDisplayName(), "Repeat! 1/1");
    }

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

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

}

Unicodeテーマを有効にした ConsoleLauncherRepeatedTestsDemo を実行すると、 コンソールには以下のように出力されます。

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

3.14. パラメーター化テスト

訳注:計算機科学の用語に従うのであれば、 argument実引数(メソッドを呼び出す側から見た引数)parameter仮引数(メソッドを呼ばれる側から見た引数) と区別して訳すべきだが、パラメーター化テストの説明では前者の argumentパラメーター と訳してしまった方が 直感的にイメージしやすいと訳者は考える。 原文でも両者の区別があいまいな箇所があるため、以降では 訳者の解釈に従って意訳している。気になる場合は、原文をあたること。

パラメーター化テストは、異なる実引数でテストを複数回実行できるようにします。 通常の @Test メソッドに似ていますが、@ParameterizedTest アノテーションを 使って宣言します。各呼び出しに対するパラメーターを生成するための ソース (source) を少なくとも1つは宣言し、そのパラメーターはテストメソッドで 消費 (consume) する必要があります。

以下は、パラメーターのソースとして String 配列を指定するために @ValueSource アノテーションを使用した例を示しています。

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

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

palindromes(String) ✔
├─ [1] racecar ✔
├─ [2] radar ✔
└─ [3] able was I ere I saw elba ✔
パラーメーター化テストは、現在 実験中 の機能です。 詳細は、実験的なAPI の表を確認してください。

3.14.1. 必要なセットアップ

パラメーター化テストを使うには、junit-jupiter-params への依存を追加する必要があります。 詳細は、依存関係メタデータ を参照してください。

3.14.2. パラメーターの消費

パラメーター化テストのメソッドは、通常は設定されたソース (パラメーターのソース 参照) からパラメーターのインデックスとメソッドの仮引数のインデックスが1対1となるように パラメーターを 消費 します (@CsvSource の例を参照)。 しかし、ソースのすべてのパラメーターを単一のオブジェクトに 集約 してメソッドに渡すこともできます (パラメーターの集約 参照)。 また、ParameterResolver を使って追加の引数を渡すこともできます (例えば、TestInfoTestReporter のインスタンスなど)。 パラメーター化テストメソッドは、次のルールに従って仮引数を宣言しなければなりません。

  • 最初に、インデックス付き引数 (indexed argument) を0個以上宣言します

  • その次に、アグリゲーター (aggregator) を0個以上宣言します

  • 最後に、ParameterResolver によって渡される引数を0個以上宣言します

ここで、インデックス付き引数 (indexed argument) は、ArgumentsProvider が生成した Arguments 中の指定されたインデックスに対するパラメーターです。 このパラメーターは、パラメーター化テストメソッドの仮引数リストで 同じインデックスに実引数として渡されます。 アグリゲーター (aggregator) は、ArgumentsAccessor 型または @AggregateWith アノテーションが付与された仮引数です。

3.14.3. パラメーターのソース

JUnit Jupiterは、あらかじめいくつかの ソース アノテーションを提供しています。 以降では、これらのアノテーションについて簡単な説明とサンプルを示します。 より詳細な情報は、org.junit.jupiter.params.provider パッケージのJavaDocを参照してください。

@ValueSource

@ValueSource は、最もシンプルなソースの1つです。リテラル値の配列を指定できますが、 パラメーター化テストの1回の実行につき1つのパラメーターしか与えることができません。

@ValueSource では、以下の型のリテラル値がサポートされています。

  • short

  • byte

  • int

  • long

  • float

  • double

  • char

  • java.lang.String

  • java.lang.Class

例えば、次の @ParameterizedTest メソッドは、値 1, 2, 3 をパラメーターとして 3回呼び出されます。

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

@EnumSource は、Enum 定数を指定する便利な方法を提供します。このアノテーションでは、 省略可能な names 属性を指定すると、その列挙定数だけが使われます。 省略すると、次の例のようにすべての列挙定数が使われます。

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

@EnumSource アノテーションは、テストメソッドに渡される列挙定数をさらに細かく制御するために、 省略可能な mode 属性も提供しています。例えば、一部の名前をもつ列挙定数だけ除外したり、 正規表現で列挙定数を指定したりすることができます。

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

@MethodSource は、テストクラスまたは外部のクラスの ファクトリ メソッドによるパラメーターの指定を可能にします。

テストクラス内のファクトリメソッドは、@TestInstance(Lifecycle.PER_CLASS) アノテーションが付与されていない限り、static でなければいけません。 一方、外部クラスのファクトリメソッドは、常に static でなければいけません。 これらのファクトリメソッドは、引数を受け取ることもできません。

ファクトリメソッドは、 @ParameterizedTest メソッドの各呼び出しに対して 実際の引数として渡される パラメーターストリーム を生成しなければなりません。 一般的に言えば、これは ArgumentsStream (つまり、Stream<Arguments>) となりますが、実際の戻り値は様々な型をとりえます。ここでいう "ストリーム" とは、 JUnitが確実に Stream へ変換できるものであれば何でも構いません。例えば、 Stream, DoubleStream, LongStream, IntStream, Collection, Iterator, Iterable, オブジェクト配列, プリミティブ型の配列などです。 ストリームの "パラメーター" は、 Arguments のインスタンス、オブジェクト配列 (つまり、Object[])、あるいはパラメーター化テストメソッドが1つだけしか パラメーターを受け取らない場合には単一の値となります。

パラメーターが1つしか必要なければ、次の例のようにパラメーターのインスタンスを Stream で返すことができます。

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

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

もし @MethodSource でファクトリメソッドの名前を明示的に指定しなかった場合、JUnit Jupiter は @ParameterizedTest メソッドと同じ名前をもつファクトリメソッドを自動的に探します。

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

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

プリミティブ型のストリーム (DoubleStream, IntStream, LongStream) もサポートされています。

@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…​)arguments(Object…​) の代わりに使用することもできます。

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

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

次の例で示すように、メソッドの完全修飾名 を指定することで、外部の staticファクトリ メソッドを参照することもできます。

package example;

import java.util.stream.Stream;

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

class ExternalMethodSourceDemo {

    @ParameterizedTest
    @MethodSource("example.StringsProviders#tinyStrings")
    void testWithExternalMethodSource(String tinyString) {
        // test with tiny string
    }
}

class StringsProviders {

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

@CsvSource は、パラメーターのリストをカンマ区切りの値(String リテラル) で指定できるようにします。

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

@CsvSource は、シングルクォート ' を引用符として使います。 上の例の 'baz, qux' と、以下のテーブルを参照してください。 引用符で囲まれた空の値 '' は空の String になりますが、 (引用符で囲まれない)完全に の値は null と解釈されます。 null の代入先の型がプリミティブ型の場合は、 ArgumentConversionException が発生します。

Example Input Resulting Argument List

@CsvSource({ "foo, bar" })

"foo", "bar"

@CsvSource({ "foo, 'baz, qux'" })

"foo", "baz, qux"

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

"foo", ""

@CsvSource({ "foo, " })

"foo", null

@CsvFileSource

@CsvFileSource は、クラスパスにあるCSVファイルを使えるようにします。 CSVファイルの各行は、パラメーター化テストの1回の実行になります。

@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSource(String first, int second) {
    assertNotNull(first);
    assertNotEquals(0, second);
}
two-column.csv
Country, reference
Sweden, 1
Poland, 2
"United States of America", 3
@CsvSource のシンタックスとは異なり、@CsvFileSource はダブルクォート " を引用符として使います。上の例の、"United States of America" を参考にしてください。 引用符で囲まれた空の値 "" は空の String になりますが、 (引用符で囲まれない)完全に の値は null と解釈されます。 null の代入先の型がプリミティブ型の場合は、 ArgumentConversionException が発生します。
@ArgumentsSource

@ArgumentsSource は、カスタムの再利用可能な ArgumentsProvider を指定する場合に使えます。

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

public class MyArgumentsProvider implements ArgumentsProvider {

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

3.14.4. パラメーターの変換

拡大変換

JUnit Jupiterは、@ParameterizedTest に与えられたパラメーターに対して、 プリミティブ型の 拡大変換 をサポートしています。例えば、@ValueSource(ints = { 1, 2, 3 }) アノテーションが付与されたパラメーター化テストでは、テストメソッドの仮引数を int 型だけでなく longfloatdouble 型でも宣言することができます。

暗黙的な変換

@CsvSource のようなユースケースをサポートするために、JUnit Jupiter は多くの組み込みの型変換を提供しています。 変換処理は、メソッドの仮引数の型に依存します。

例えば、@ParameterizedTestTimeUnit 型の仮引数を宣言し、 ソースから実際に渡される型が String の場合、その文字列は対応する TimeUnit 列挙定数に自動的に変換されます。

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

String インスタンスは、今のところ次のようなターゲット型に暗黙的に変換されます。

Target Type Example

boolean/Boolean

"true"true

byte/Byte

"1"(byte) 1

char/Character

"o"'o'

short/Short

"1"(short) 1

int/Integer

"1"1

long/Long

"1"1L

float/Float

"1.0"1.0f

double/Double

"1.0"1.0d

Enum subclass

"SECONDS"TimeUnit.SECONDS

java.io.File

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

java.lang.Class

"java.lang.Integer"java.lang.Integer.class (use $ for nested classes, e.g. "java.lang.Thread$State")

java.lang.Class

"byte"byte.class (primitive types are supported)

java.lang.Class

"char[]"char[].class (array types are supported)

java.math.BigDecimal

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

java.math.BigInteger

"1234567890123456789"new BigInteger("1234567890123456789")

java.net.URI

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

java.net.URL

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

java.nio.charset.Charset

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

java.nio.file.Path

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

java.time.Instant

"1970-01-01T00:00:00Z"Instant.ofEpochMilli(0)

java.time.LocalDateTime

"2017-03-14T12:34:56.789"LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)

java.time.LocalDate

"2017-03-14"LocalDate.of(2017, 3, 14)

java.time.LocalTime

"12:34:56.789"LocalTime.of(12, 34, 56, 789_000_000)

java.time.OffsetDateTime

"2017-03-14T12:34:56.789Z"OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)

java.time.OffsetTime

"12:34:56.789Z"OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)

java.time.YearMonth

"2017-03"YearMonth.of(2017, 3)

java.time.Year

"2017"Year.of(2017)

java.time.ZonedDateTime

"2017-03-14T12:34:56.789Z"ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)

java.util.Currency

"JPY"Currency.getInstance("JPY")

java.util.Locale

"en"new Locale("en")

java.util.UUID

"d043e930-7b3b-48e3-bdbe-5a3ccfb833db"UUID.fromString("d043e930-7b3b-48e3-bdbe-5a3ccfb833db")

String → Object変換のフォールバック

上の表で示した文字列からターゲット型への暗黙的な変換に加えて、JUnit Jupiter は String 型からターゲット型に自動変換するフォールバックの仕組みも提供しています。 この変換は、ターゲット型が以下に示す ファクトリメソッドファクトリコンストラクタ を1つだけ宣言している場合に機能します。

  • ファクトリメソッド: ターゲット型に宣言された非privateの static メソッドで、 String 型の引数を1つだけ受け取ってターゲット型のインスタンスを返す。 メソッドの名前は任意であり、特定のルールに従う必要はない。

  • ファクトリコンストラクタ: ターゲット型に宣言された非privateのコンストラクタで、 String 型の引数を1つだけ受け取ってインスタンスを作る。

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

例えば、次の @ParameterizedTest メソッドでは、Book 型の引数は Book.fromTitle(String) ファクトリメソッドに本のタイトルとして "42 Cats" を渡して実行することで作られます。

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

public class Book {

    private final String title;

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

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

    public String getTitle() {
        return this.title;
    }
}
明示的な変換

暗黙的な変換を使う代わりに、@ConvertWith アノテーションで 仮引数の変換に使う ArgumentConverter を明示的に指定することもできます。

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

    assertNotNull(TimeUnit.valueOf(argument));
}

public class ToStringArgumentConverter extends SimpleArgumentConverter {

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

明示的な型変換器はテストまたは拡張機能の作者に実装されることを想定しています。 そのため、junit-jupiter-params モジュールは参照実装として JavaTimeArgumentConverter の1つだけしか提供していません。これは、合成アノテーション JavaTimeConversionPattern 経由で使用できます。

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

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

3.14.5. パラメーターの集約

デフォルトでは、@ParameterizedTest のメソッドに与えられる各パラメーター は、1つの仮引数に対応します。結果として、ソースが多数のパラメーターを生成する場合、 メソッドシグネチャが大きく(仮引数が多く)なります。

そのような場合には、複数の仮引数を定義する代わりに ArgumentsAccessor を使うことができます。このAPIを使うと、テストメソッドに渡した単一の ArgumentsAccessor 経由ですべてのパラメーターにアクセスすることができます。 加えて、 暗黙的な変換 で説明した型変換もサポートされます。

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

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

ArgumentsAccessor 型の仮引数には、自動的に ArgumentsAccessor のインスタンスが注入されます。

カスタムアグリゲーター

ArgumentsAccessor を使って @ParameterizedTest メソッドのパラメーターに 直接アクセスする以外に、JUnit Jupiterはカスタムの再利用可能な アグリゲーター (aggregator) もサポートしています。

カスタムアグリゲーターを使うには、ArgumentsAggregator インターフェースを実装し、 @ParameterizedTest メソッドの仮引数に @AggregateWith アノテーションを使って登録します。集約した結果は、 パラメーター化テストが実行されるときに 対象の仮引数に対する実引数として渡されます。

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

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

複数のパラメーター化テストメソッドに対して何回も @AggregateWith(MyTypeAggregator.class) を宣言していることに気付いたら、@AggregateWith(MyTypeAggregator.class) をメタアノテーションとして付与した @CsvToMyType のようなカスタム 合成アノテーション を作りたくなるでしょう。 次の例は、実際にカスタムの @CsvToPerson アノテーションを作る例を示しています。

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

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

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

デフォルトでは、パラメーター化テストの表示名には、呼び出しインデックスとすべてのパラメーターの String 表現が含まれます。ただし、@ParameterizedTest アノテーションの name 属性を指定すれば、次の例のようにカスタマイズすることもできます。

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

上のメソッドを ConsoleLauncher で実行すると、以下のような出力となります。

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

次のプレースホルダーがサポートされています。

Placeholder Description

{index}

現在の呼び出しインデックス(1始まり)

{arguments}

カンマ区切りの完全なパラメーターのリスト

{0}, {1}, …​

個々のパラメーター

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

パラメーター化テストの各呼び出しは、通常の @Test メソッドと同じライフサイクルをもちます。 例えば、@BeforeEach メソッドが各呼び出しの前に実行されます。 動的テスト と同様に、パラメーター化テストの各呼び出しは、 IDEのテストツリーの中に1つずつ表示されます。同じテストクラスの中で、 @Test メソッドと @ParameterizedTest メソッドを混在させるのも自由です。

ParameterResolver 拡張を @ParameterizedTest メソッドと使いたいかもしれません。 その場合は、パラメーターのソースから渡される引数を先に宣言する必要があります。 テストクラスは、パラメーター化テストだけでなく通常のテストを含んでいる可能性もあるので、 パラメーターをライフサイクルメソッド (例えば、@BeforeEach) やテストコンストラクタに渡すことができません。

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

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

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

3.15. テストテンプレート

@TestTemplate メソッドは、通常のテストケースではなく、テストケースのテンプレートです。 登録したプロバイダが返す実行コンテキストの数に応じて、複数回呼び出されるよう設計されています。 そのため、テストテンプレートは TestTemplateInvocationContextProvider と一緒に使う必要があります。 テストテンプレートメソッドの各呼び出しは、ライフサイクルコールバックや拡張機能など、 通常の @Test メソッドの実行と同じように振る舞います。使用例については、 テストテンプレートに実行コンテキストを与える を参照してください。

3.16. 動的テスト

アノテーション で説明したJUnit Jupiterの標準の @Test アノテーションは、JUnit 4の @Test アノテーションととてもよく似ています。 どちらもテストケースを実装するメソッドを示します。これらのテストケースは、 完全にコンパイル時に指定され、その振る舞いが実行時には変えられないという意味で、 静的なものです。 前提条件 (Assumption) の機能は、動的な振る舞いの基本を 提供しますが、表現力は意図的に限定されています。

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

@Test メソッドとは異なり、@TestFactory メソッド自身はテストケースではなく、 テストケースのファクトリとなります。ファクトリの出力が動的テストになります。 技術的に言うと、@TestFactory メソッドは、DynamicNode インスタンスの Stream, Collection, Iterable, Iterator, 配列のいずれかを 返さなければなりません。DynamicNode のインスタンス化可能なサブクラスには、 DynamicContainerDynamicTest があります。DynamicContainer インスタンスは、表示名 と子どもの DynamicNode リストで構成され、 入れ子になった DynamicNode の階層を作成できるようになっています。 DynamicTest インスタンスが遅延実行されることで、動的かつ非決定的な テストケースの生成を実現します。

@TestFactory が返した Stream は、stream.close() 呼び出しによって 適切にクローズされます。これにより、Files.lines() などのリソースを 安全に使うことができます。

@Test メソッドと同じように、@TestFactory メソッドも private または static であってはいけません。また、ParameterResolvers で解決される パラメーターを宣言することもできます。

DynamicTest は、実行時に生成されるテストケースで、表示名Executable で構成されます。Executable は、@FunctionalInterface であり、 動的テストの実装が ラムダ式 または メソッド参照 で与えられることを意味します。

動的テストのライフサイクル
動的テストの実行ライフサイクルは、通常の @Test とはかなり違います。 特に、個々の動的テストに対しては、ライフサイクルコールバックはありません。 これは、@BeforeEach@AfterEach メソッドとその拡張コールバックが、 @TestFactory メソッドに対して実行され、個々の 動的テスト に対しては実行されないことを意味しています。言い換えれば、動的テストのラムダ式から テストインスタンスのフィールドにアクセスしていたとしても、同じ @TestFactory メソッドから生成された個々の動的テストを実行する間は、コールバックメソッドにも 拡張機能にもそのフィールドはリセットされないのです。

JUnit Jupiter 5.3.0 の時点では、動的テストは常にファクトリメソッドで 生成しなければなりません。ただし、将来のリリースで、登録の仕組みが追加されるかもしれません。

動的テストは現在 実験中 の機能です。詳細は、 実験的なAPI の表を参照してください。

3.16.1. 動的テストの例

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

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

その次の5つのメソッドは、DynamicTest インスタンスの Collection, Iterable, Iterator, Stream を生成するとてもシンプルな例です。ほとんどの例は、 動的な振る舞いではなく、単にサポートされている戻り値の型をデモしています。 しかし、dynamicTestsFromStream()dynamicTestsFromIntStream() は、 与えられた文字列リストや数値の範囲に対して簡単に動的なテストを生成できることを 示しています。

その次のメソッドは、真に動的な性質のものです。generateRandomNumberOfTests() は、乱数を生成する Iterator、表示名の生成器、テストの実行器の3つを実装し、 これらを DynamicTest.stream() に渡しています。 generateRandomNumberOfTests() の非決定的な振る舞いは、 もちろんテストの再現性に反するため注意して使うべきですが、 動的テストの高い表現力とパワーを示すのに役立っています。

最後のメソッドは、DynamicContainer を使って動的テストを入れ子階層にする例を示しています。

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

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

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

class DynamicTestsDemo {

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

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

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

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

    @TestFactory
    DynamicTest[] dynamicTestsFromArray() {
        return new DynamicTest[] {
            dynamicTest("7th dynamic test", () -> assertTrue(true)),
            dynamicTest("8th dynamic test", () -> assertEquals(4, 2 * 2))
        };
    }

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

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

    @TestFactory
    Stream<DynamicTest> generateRandomNumberOfTests() {

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

            Random random = new Random();
            int current;

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

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

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

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

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

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

}

3.17. 並列実行

デフォルトでは、JUnit Jupiterのテストはシングルスレッドで逐次実行されます。バージョン 5.3からは、オプトインの機能として(例えば高速化のために)テストの並列実行を利用できます。 並列実行を有効にするには、単に junit-platform.properties ファイルで 設定パラメーター junit.jupiter.execution.parallel.enabledtrue を指定するだけです(その他の選択肢については、設定パラメーター を参照)。

有効にすると、JUnit Jupiterエンジンは与えられた 設定 と宣言的に指定された 同期化 に従って、 テストツリーのすべてのレベルのテストを並列実行します。なお、 標準出力/標準エラー出力のキャプチャ 機能は別途有効にする必要がある点に注意してください。

テストの並列実行は、今のところ 実験的な 機能です。JUnitチームがこの機能を改善し、 最終的には 昇格 できるよう、試しに使ってみてフィードバックをください。

3.17.1. 設定

希望する並列数やプールの最大サイズなどのプロパティは、 ParallelExecutionConfigurationStrategy を使って設定できます。 JUnit Platformは、すぐに使える2つの実装(dynamicfixed)を提供しています。 なお、代わりに独自の custom 戦略を実装することもできます。

並列戦略を選択するには、設定パラメーター junit.jupiter.execution.parallel.config.strategy に次のいずれかを指定します。

dynamic

利用可能なプロセッサ/コア数に、設定パラメーター junit.jupiter.execution.parallel.config.dynamic.factor の値 (デフォルトは 1)を掛けた数に基づいて並列数を計算します。

fixed

設定パラメーター junit.jupiter.execution.parallel.config.fixed.parallelism の値を並列数として使用します。

custom

設定パラメーター junit.jupiter.execution.parallel.config.custom.class で指定された独自の ParallelExecutionConfigurationStrategy 実装を使って、 並列数を決定します。

もし並列戦略が指定されていなかった場合、JUnit Jupiterは dynamic 戦略を使います。 つまり、並列数は利用可能なプロセッサ/コア数の数と同じになります。

3.17.2. 同期化

JUnit Jupiterは、異なるテスト間で共有リソースを使うときの実行モデルを変更し、 同期化を可能にするためのアノテーションベースの仕組みを2つ org.junit.jupiter.api.parallel パッケージで提供しています。

並列実行が有効な場合、デフォルトではすべてのクラスとメソッドが同時に実行されます。 @Execution アノテーションを使うことで、アノテーションが付与された要素と (もしあれば)サブ要素の実行モデルを変更することができます。 次の2つのモードが利用可能です。

SAME_THREAD

親と同じスレッドで実行することを強制します。例えば、テストメソッドに指定した場合、 該当メソッドはテストクラスの @BeforeAll メソッドや @AfterAll メソッドを 実行するスレッドと同じスレッドで実行されます。

CONCURRENT

リソース制約で同一スレッドでの実行が強制されない限り、別スレッドで並列実行されます。

加えて、@ResourceLock アノテーションを使うと、テストクラスやテストメソッドが 同期化されたアクセスを必要とする共有リソースを使用していると宣言できます。

仮に次のサンプルのテストが並列実行されたとすると、結果は不安定 (flaky) 、 つまり成功したり失敗したりします。 というのも、同じシステムプロパティを読み書きする競合状態があるからです (訳注: @ResourceLock による同期化がない仮定の話)。

@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 canSetCustomPropertyToFoo() {
        System.setProperty("my.prop", "foo");
        assertEquals("foo", System.getProperty("my.prop"));
    }

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

共有リソースへのアクセスが @ResourceLock アノテーションで宣言されている場合、 JUnit Jupiterエンジンはこの情報を活用し、互いにコンフリクトするテストが同時に 実行されないよう保証します。

使用されるリソースを一意に特定する文字列に加えて、アクセスモードを指定することもできます。 同じリソースの READ アクセスを要求する2つのテストは同時に実行することができますが、 READ_WRITE アクセスを要求するテストが実行中の場合は実行できません。

4. テストを実行する

4.1. IDEのサポート

4.1.1. IntelliJ IDEA

IntelliJ IDEAは、バージョン2016.2からJUnit Platform上でのテスト実行をサポートしています。 詳細は、 IntelliJ IDEAブログの投稿 を参照してください。ただし、実際にはIntelliJ IDEA 2017.3またはそれより新しいバージョンの 利用が推奨される点に注意してください。というのも、プロジェクトで利用されるAPIバージョンに応じて junit-platform-launcher, junit-jupiter-engine, junit-vintage-engine のJARを自動的にダウンロードしてくれるからです。

IntelliJ IDEAのバージョン2017.3より前のリリースは、JUnit 5 の特定のバージョンを同梱している点に注意してください。もしもJUnit Jupiter の新しいバージョンを使いたい場合、IDE内でのテスト実行はバージョンのコンフリクトで 失敗する可能性があります。そのような場合は、IntelliJ IDEAに同梱されている JUnit 5より新しいバージョンを使うために、以下の手順に従ってください。

JUnit 5の異なるバージョン (例えば、 5.3.0) を使うには、 junit-platform-launcher, junit-jupiter-engine, junit-vintage-engine の対応するバージョンのJARをクラスパスに追加する必要があります。

Gradleの追加の依存関係
// Only needed to run tests in a version of IntelliJ IDEA that bundles older versions
testRuntime("org.junit.platform:junit-platform-launcher:1.3.0")
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.3.0")
testRuntime("org.junit.vintage:junit-vintage-engine:5.3.0")
Mavenの追加の依存関係
<!-- Only needed to run tests in a version of IntelliJ IDEA that bundles older versions -->
<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-launcher</artifactId>
    <version>1.3.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.3.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <version>5.3.0</version>
    <scope>test</scope>
</dependency>

4.1.2. Eclipse

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

EclipseでJUnit 5を使うための情報は、公式ドキュメント Eclipse Project Oxygen.1a (4.7.1a) - New and NoteworthyEclipse support for JUnit 5 の節を参照してください。

4.1.3. その他のIDE

本書執筆時点では、IntelliJ IDEAとEclipse以外のIDEは、JUnit Platform 上でのテスト実行を直接はサポートしていません。しかし、JUnitチームは あなたが使っているIDEでJUnit 5を試せるように、2つの中間解を提供しています。 Console Launcher を手動で使うか、 JUnit 4ベースのランナー でテストを実行することができます。

4.2. ビルドサポート

4.2.1. Gradle

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公式ドキュメント を参照してください。

JUnit Platform Gradleプラグインは廃止

JUnitチームによって開発されていた junit-platform-gradle-plugin は、 JUnit Platform 1.2 から非推奨となり、1.3 で廃止されました。 Gradle標準の test タスクに移行してください。

設定パラメーター

Gradle標準の test タスクは、テストの発見・実行に影響を与えるJUnit Platformの 設定パラメーター を指定する専用DSL を今のところ提供していません。 しかし、以下に示すようにビルドスクリプト内でシステムプロパティを使うか、 junit-platform.properties ファイルを使うことで、設定パラメーターを指定できます。

test {
    // ...
    systemProperty 'junit.jupiter.conditions.deactivate', '*'
    systemProperties = [
        'junit.jupiter.extensions.autodetection.enabled': 'true',
        'junit.jupiter.testinstance.lifecycle.default': 'per_class'
    ]
    // ...
}
テストエンジンを設定する

テストを実行させるためには、TestEngine の実装がクラスパスに存在しなければなりません。

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

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

JUnit Platformは、JUnit 4ベースのテストも実行することができます。 そのためには、JUnit 4に対する testCompile 依存と、JUnit Vintage TestEngine 実装に対する testRuntime 依存が必要です。

dependencies {
    testCompile("junit:junit:4.12")
    testRuntime("org.junit.vintage:junit-vintage-engine:5.3.0")
}
ロギングを設定する(省略可)

JUnitは、java.util.logging パッケージ(別名、JUL)の Java Logging API を警告やデバッグ情報の出力に利用しています。設定オプションについては、 LogManager の公式ドキュメントを参照してください。

ログを Log4jLogback のような他のロギングフレームワークにリダイレクトする ことも可能です。LogManager のカスタム実装を提供するロギングフレームワークを 使う場合は、java.util.logging.manager システムプロパティに LogManager 実装クラスの 完全修飾名 を指定してください。以下は、Log4j 2.x (詳細は、 Log4j JDK Logging Adapter 参照)を設定する例です。

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

その他のロギングフレームワークは、java.util.logging のログをリダイレクトする 方法が異なります。例えば Logback では、 JULからSLF4Jへのブリッジ を実行時のクラスパスに依存関係として追加して使います。

4.2.2. Maven

JUnitチームによって開発されていた junit-platform-surefire-provider は非推奨となり、JUnit Platform 1.4で削除が予定されています。 代わりに、Maven Surefireを使用してください。

バージョン 2.22.0 から、 Maven SurefireはJUnit Platform上でテストを実行するための ネイティブサポート を提供しています。 junit5-jupiter-starter-maven プロジェクトの pom.xml ファイルがその使い方を示すとともに、Mavenビルドを設定するための出発点となるでしょう。

テストエンジンを設定する

Maven Surefireにテストを実行させるためには、TestEngine の実装が少なくとも1つ 実行時のクラスパスに必要です。

JUnit Jupiterベースのテストをサポートするためには、JUnit Jupiter APIと JUnit Jupiter TestEngine 実装に対する test スコープの依存を 次のように指定する必要があります。

<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.0</version>
        </plugin>
    </plugins>
</build>
...
<dependencies>
    ...
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.3.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.3.0</version>
        <scope>test</scope>
    </dependency>
    ...
</dependencies>
...

Maven Surefireは、JUnit 4ベースのテストも実行することができます。 そのためには、次のようにJUnit 4とJUnit Vintage TestEngine 実装に対する test スコープの依存が必要です。

...
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.0</version>
        </plugin>
    </plugins>
</build>
...
<dependencies>
    ...
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.vintage</groupId>
        <artifactId>junit-vintage-engine</artifactId>
        <version>5.3.0</version>
        <scope>test</scope>
    </dependency>
    ...
</dependencies>
...
テストクラス名でフィルタリングする

Maven Surefireプラグインは、完全修飾名が次のパターンにマッチするテストクラスを探します。

  • **/Test*.java

  • **/*Test.java

  • **/*Tests.java

  • **/*TestCase.java

なお、すべてのネストクラス(staticな内部クラスも含む)はデフォルトで除外されます。

ただし、pom.xml ファイルの include および exclude ルールを明示的に設定することで、デフォルトの動作を変更することもできます。 例えば、Maven Surefireがstaticな内部クラスを除外しないようにするなどです。

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

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

タグでフィルタリングする

次のように、タグまたは タグ式 を使ってテストをフィルタリングすることもできます。

  • タグ または タグ式 をテスト対象に含めるには、groups を指定します。

  • タグ または タグ式 をテスト対象から除外するには、excludedGroups を指定します。

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

configurationParameters プロパティにJavaの Properties ファイル形式の キー・値のペアを与えるか、junit-platform.properties ファイルを与えることで、 テストの発見・実行に影響を与えるJUnit Platformの 設定パラメーター を指定できます。

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

4.2.3. Ant

Ant のバージョン 1.10.3 から、 JUnit Platform上でのテスト実行をネイティブサポートするために junitlauncher タスクが導入されました。junitlauncher タスクは、単にJUnit Platform を起動して、選択したテストを渡すだけの責務をもちます。その後、JUnit Platform が登録されたテストエンジンにテストの発見・実行を移譲します。

junitlauncher タスクは、 ユーザーがテストエンジンに実行させたいテストを選択するための リソース集合 のように、Antの既存機能と可能な限り一貫性を保とうとしています。 これにより、Antの他のコアタスクと比べても、一貫性があり直感的なものになっています。

Ant 1.10.3 でリリースされた junitlauncher タスクは、 JUnit Platformを起動する基本的かつ最低限のサポートだけを提供します。 追加の機能強化 (異なるJVMにforkしてのテスト実行サポートを含む) は、 今後のAntリリースで利用可能になる予定です。

junit5-jupiter-starter-ant プロジェクトの build.xml ファイルが使い方を示しています。

基本的な使い方

次の例は、単一のテストクラス (org.myapp.test.MyFirstJUnit5Test) を選択するように junitlauncher タスクを設定する方法を示します。

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

<!-- ... -->

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

test 要素で実行したい単一のテストクラスを指定することができます。 classpath 要素でJUnit Platformを起動するのに使うクラスパスを指定することができます。 このクラスパスは、実行するテストクラスを探すのにも使われます。

次の例は、複数の場所からテストクラスを選択するように junitlauncher タスクを設定する方法を示します。

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

上の例のように testclasses 要素を使うと、異なる場所にある複数のテストクラスを選択できます。

使い方と設定オプションに関する詳細は、Ant公式ドキュメントの junitlauncher タスク を参照してください。

4.3. Console Launcher

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

すべての依存関係を含んだ実行可能な junit-platform-console-standalone-1.3.0.jar がMavenセントラルリポジトリの junit-platform-console-standalone ディレクトリで公開されています。スタンドアロン版の ConsoleLauncher は、次のように 実行する ことができます。

java -jar junit-platform-console-standalone-1.3.0.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          ]
終了コード
ConsoleLauncher は、コンテナやテストが失敗するとステータスコード 1 で終了します。 テストが見つからず、かつコマンドラインオプション --fail-if-no-tests が指定されていた場合は、 ConsoleLauncher はステータスコード 2 で終了します。その他の場合は、終了コードは 0 です。

4.3.1. オプション

Usage: ConsoleLauncher [-h] [--disable-ansi-colors] [--fail-if-no-tests] [--scan-modules]
                       [--scan-classpath[=PATH[;|:PATH...]]]... [--details=MODE]
                       [--details-theme=THEME] [--reports-dir=DIR]
                       [--config=KEY=VALUE]... [--exclude-package=PKG]...
                       [--include-package=PKG]... [-c=CLASS]... [-cp=PATH[;|:PATH...]]...
                       [-d=DIR]... [-e=ID]... [-E=ID]... [-f=FILE]... [-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).
      --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. 引数ファイル (@ファイル)

プラットフォームによっては、多数のオプションや長い引数をもつコマンドラインを作ろうとすると、 システムのコマンドライン文字数の上限に引っかかるかもしれません。

バージョン1.3から、ConsoleLauncher引数ファイル (@ファイル としても知られる) をサポートします。引数ファイルは、コマンドに与える引数を記述したファイルです。 内部で使用している picocli コマンドラインパーサーは、 @ 文字から始まる引数を見つけると、そのファイルの中身を引数リストとして展開します。

ファイル内の引数は、スペースまたは改行で分割できます。引数自身が空白を含む場合は、 引数全体をダブルクォートかシングルクォートで囲まなければなりません。 例えば、"-f=My Files/Stuff.java" のようになります。

引数ファイルが存在しないか読み込めない場合は、無視されずにそのリテラル文字列が引数として扱われます。 その結果、 "unmatched argument" エラーメッセージが表示される結果になりがちです。 picocli.trace システムプロパティを DEBUG に設定してからコマンドを実行すれば、 このようなエラーをトラブルシュートすることができます。

複数の @ファイル をコマンドラインに指定することもできます。指定するパスは、 カレントディレクトリからの相対パスにするか、絶対パスにします。

実際の引数が @ 文字から始まる場合は、@ をもう1つ追加してエスケープします。 例えば、@@somearg@somearg と解釈され、展開はされません。

4.4. JUnit Platformの実行にJUnit 4を使う

JUnitPlatform ランナーは、JUnit Platformのプログラミングモデルに従ったテスト (例えば、JUnit Jupiterのテストクラス)をJUnit 4環境で実行可能にするための JUnit 4ベースの Runner です。

テストクラスに @RunWith(JUnitPlatform.class) アノテーションを付与すると、 まだJUnit Platformを直接はサポートしていないけれどもJUnit 4ならサポートしている IDEやビルドシステムでも実行できるようになります。

JUnit PlatformはJUnit 4にない機能をもっているため、特にレポート周り (表示名 vs. 技術名 を参照)など、 JUnit Platformの機能のサブセットしかサポートすることができません。 しかし、さしあたっては JUnitPlatform ランナーを使って始めるのが簡単です。

4.4.1. セットアップ

クラスパスに以下のライブラリとその依存ライブラリが必要です。Group IDやArtifact ID、 バージョンの詳細については、 依存関係メタデータ を参照してください。

明示的な依存関係
  • junit-platform-runnertest スコープ: JUnitPlatform ランナーが含まれる

  • junit-4.12.jartest スコープ: JUnit 4を使ってテストを実行するため

  • junit-jupiter-apitest スコープ: @Test などJUnit Jupiterを使ってテストを書くためのAPI

  • junit-jupiter-enginetest runtime スコープ: JUnit Jupiterのための TestEngine API実装

推移的な依存関係
  • junit-platform-suite-apitest スコープ

  • junit-platform-launchertest スコープ

  • junit-platform-enginetest スコープ

  • junit-platform-commonstest スコープ

  • opentest4jtest スコープ

4.4.2. 表示名 vs. 技術名

訳注:本文書では Display Name に対して 表示名 という訳語を使用し、 本節で登場する Technical Name に対しては 技術名 という訳語を使用した。 しかし、後者は直感的ではないと思うので、適切な訳があればフィードバックをいただきたい。

@RunWith(JUnitPlatform.class) ランナーで実行されるクラスにカスタムの 表示名 (display name) を定義するためには、@SuiteDisplayName アノテーションを付与します。

デフォルトでは、 表示名 はテストの成果物に使われます。しかし、Gradleや Mavenのようなビルドツールが JUnitPlatform ランナーを使う場合、 生成されるテストレポートには、テストクラスの単純名や 特殊文字を含むカスタム表示名ではなく、完全修飾クラス名のような 技術名 (technical name) が必要になることがあります。 レポート出力で 技術名 を有効にするためには、 @RunWith(JUnitPlatform.class) と一緒に @UseTechnicalNames アノテーションを宣言してください。

@UseTechnicalNames を付与すると、@SuiteDisplayName でしたカスタム表示名は無視される点に注意してください。

4.4.3. 単一のテストクラス

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

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

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

@RunWith(JUnitPlatform.class)
public class JUnit4ClassDemo {

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

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

}

4.4.4. テストスイート

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

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

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

JUnit4SuiteDemo は、example パッケージとそのサブパッケージから すべてのテストを探して実行します。デフォルトでは、名前が Test で始まるか、 Test または Tests で終わるテストクラスだけを実行します。

追加の設定オプション
@SelectPackages 以外にも、テストを検索したりフィルタリングしたりするための 設定オプションがあります。詳細は、 Javadoc を参照してください。

4.5. 設定パラメーター

どのテストクラスやテストエンジンを含むか、どのパッケージを検索するか、といったことを プラットフォームに指示するだけでなく、特定のテストエンジンや拡張機能に固有の 追加の設定パラメーターを渡したい場合もあります。例えば、JUnit Jupiterの TestEngine は、次のユースケースのために 設定パラメーター をサポートしています。

設定パラメーター は、テキストベースのキーと値のペアで、JUnit Platform で動作するテストエンジンに次のいずれかの方法で渡されます。

  1. LauncherDiscoveryRequestBuilder クラスの configurationParameter() または configurationParameters() メソッドを使って構築したリクエストを Launcher API に渡します。JUnit Platform が提供するツールを使ってテストを実行する場合、設定パラメーターは次のように指定できます。

  2. JVMシステムプロパティ

  3. JUnit Platform設定ファイル: クラスパスのルートに junit-platform.properties という名前で配置され、Javaの Properties ファイルと同じシンタックスに従います。

設定パラメーターは、上で定義された順番に従って解決されます。つまり、 Launcher に直接与えられた設定パラメーターの方が、システムプロパティや設定ファイルで 与えられたものより優先されます。同様に、システムプロパティで与えられた設定パラメーターの方が、 設定ファイルで与えられたものより優先されます。

4.6. タグ式

タグ式は、 !, &, | 演算子からなるブール式です。 演算子の優先順位を調整するために、() も使えます。

Table 1. 演算子 (優先順位の降順)
演算子 意味 結合性

!

not

right

&

and

left

|

or

left

テストに直交するタグを付与している場合、実行するテストを選択するのにタグ式は便利です。 テストの種類 (例えば、micro, integration, end-to-end) と機能 (例えば、 foo, bar, baz) のタグを付与すると、次のようなタグ式が使えます。

タグ式 該当するテスト

foo

foo に対するすべてのテスト

bar | baz

bar または baz に対するすべてのテスト

bar & baz

bar かつ baz に対するすべてのテスト

foo & !end-to-end

foo に対するテストのうち、end-to-end レベル以外のすべてのテスト

(micro | integration) & (foo | baz)

foo または baz に対するテストのうち、micro または integration レベルのすべてのテスト

4.7. 標準出力/標準エラー出力のキャプチャ

バージョン1.3から、JUnit Platformは System.outSystem.err への出力をキャプチャする機能をオプトインでサポートします。有効にするには、 junit.platform.output.capture.stdout および junit.platform.output.capture.stderr 設定パラメーターtrue を指定するだけです。 加えて、junit.platform.output.capture.maxBuffer を使用することで、 テストやコンテナ毎に使用されるバッファサイズの最大値を設定することができます。

有効にすると、JUnit Platformは該当の出力をキャプチャし、stdout または stderr キーを使ってレポート情報に格納した上で、テストやコンテナの終了をレポートする直前に TestExecutionListener インスタンスへ通知します。

キャプチャされた出力は、コンテナやテストを実行するのに使用したスレッドからの出力だけを 含む点に注意してください。その他のスレッドからの出力は除外されます。というのも、 特に テストを並列実行している 場合には、 出力をテストやコンテナ単位でひも付けるのは不可能だからです。

出力のキャプチャは、今のところ 実験的な 機能です。JUnitチームがこの機能を改善し、 最終的には 昇格 できるよう、試しに使ってみてフィードバックをください。

5. 拡張モデル

5.1. 概要

JUnit 4の Runner, @Rule, @ClassRule などの拡張ポイントとは異なり、 JUnit Jupiterの拡張モデルは、単一の一貫したコンセプトである Extension API で構成されます。ただし、Extension 自身はただのマーカーインターフェースである点に 注意してください。

5.2. 拡張機能の登録

拡張機能は、 @ExtendWith を使って 宣言的に 登録するか、 @RegisterExtension を使って 手続き的に 登録するか、あるいは Javaの ServiceLoader の仕組みを使って 自動的に 登録することができます。

5.2.1. 宣言的な登録

開発者は、テストインターフェースやテストクラス、テストメソッド、 合成アノテーション@ExtendWith(…​) アノテーションを付与し、 拡張機能として登録したいクラスの参照を指定することで 1つ以上の拡張機能を 宣言的に 登録することができます。

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

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

特定のクラスとそのサブクラスのすべてのテストに RandomParametersExtension を登録したい場合は、テストクラスに次のようにアノテーションを付与します。

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

複数の拡張機能は、次のように同時に登録することができます。

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

別の方法としては、次のように個別に登録することができます。

@ExtendWith(FooExtension.class)
@ExtendWith(BarExtension.class)
class MySecondTests {
    // ...
}
拡張機能の登録順序
@ExtendWith で宣言的に登録された拡張機能は、ソースコード上での宣言順で実行されます。 例えば、MyFirstTestsMySecondTests のテスト実行は、FooExtensionBarExtension によって まさにこの順序で 拡張されます。

5.2.2. 手続き的な登録

開発者は、テストクラスのフィールドに @RegisterExtension アノテーションを付与することで、 拡張機能を 手続き的に 登録することができます。

拡張機能を @ExtendWith宣言的に 登録する場合、拡張機能の設定はアノテーション経由でしか行なえません。 しかし、拡張機能を @RegisterExtension で登録する場合は、 拡張機能のコンストラクタやstaticファクトリメソッド、 ビルダーAPIに引数を渡して 手続き的に 設定することができます。

@RegisterExtension フィールドは、private または null (評価時) であってはなりませんが、static か非 static かはどちらでもかまいません。
staticフィールド

@RegisterExtension フィールドが static の場合、その拡張機能は @ExtendWith でクラスレベルに登録される拡張機能の後で登録されます。 そのような staticな拡張機能 は、実装する拡張APIに制限がありません。 そのため、staticフィールドとして登録される拡張機能は、 BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor のようなクラスレベルやインスタンスレベルの拡張APIだけでなく、 BeforeEachCallback などのようなメソッドレベルの拡張APIも実装できます。

次の例では、テストクラスの server フィールドは WebServerExtension がサポートするビルダーパターンを使って手続き的に初期化されます。設定された WebServerExtension は、クラスレベルの拡張機能として自動的に登録され、 例えば、すべてのテストを開始する前にサーバーを起動したり、 すべてのテストが完了した後でサーバーを停止したりすることができます。 加えて、@BeforeEach, @AfterEach, @Test メソッドだけでなく、 @BeforeAll@AfterAll のようなstaticなライフサイクルメソッドも 必要であれば server フィールド経由でこの拡張機能のインスタンスにアクセスできます。

staticフィールド経由で登録される拡張機能
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 フィールドがありません。しかし、 アノテーションを使うと、コンパイラにstaticフィールドを生成させることができます。 先に述べた通り、@RegisterExtension フィールドは private または null になってはならないため、private フィールドを生成する @JvmStatic アノテーションをKotlinで 使うことはできません。 代わりに、@JvmField アノテーションを使ってください。

次の例は、前節の WebServerDemo をKotlinに移植したものです。

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

    companion object {
        @JvmField
        @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 フィールドが非static (つまり、インスタンスフィールド) の場合、その拡張機能はテストクラスがインスタンス化され、登録済みの各 TestInstancePostProcessor がテストインスタンスの事後処理 (拡張機能のインスタンスをアノテーションの付与されたフィールドに注入するなど) をした後で登録されます。そのため、インスタンスフィールドの拡張機能BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor のようなクラスレベルやインスタンスレベルの拡張APIを実装しても意味がありません。 デフォルトでは、インスタンスフィールドの拡張機能は @ExtendWith でメソッドレベルに登録される拡張機能より 後で 登録されます。しかし、 テストクラスが @TestInstance(Lifecycle.PER_CLASS) モードに設定されている場合は、 @ExtendWith でメソッドレベルに登録される拡張機能の 前に 登録されます。

次の例では、テストクラスの docs フィールドは lookUpDocsDir() メソッドを呼び出した結果を DocumentationExtension のstaticファクトリメソッド forPath() に渡すことで、手続き的に初期化されます。設定された DocumentationExtension は、メソッドレベルの拡張機能として自動的に登録されます。 加えて、@BeforeEach, @AfterEach, @Test メソッドは 必要であれば docs フィールド経由でこの拡張機能のインスタンスにアクセスできます。

インスタンスフィールド経由で登録される拡張機能
class DocumentationDemo {

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

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

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

5.2.3. 自動的な登録

アノテーションを使った拡張機能の 宣言的な登録手続き的な登録 に加えて、 JUnit JupiterはJavaの java.util.ServiceLoader の仕組みを使った拡張機能の グローバルな登録 もサポートしています。 これにより、クラスパスから利用可能なサードパーティの拡張機能を自動検出し、 自動的に登録させることができます。

具体的には、JARファイルの /META-INF/services フォルダの中に org.junit.jupiter.api.extension.Extension という名前のファイルを作成し、 クラスの完全修飾名を指定することで拡張機能を登録できます。

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

自動検出は高度な機能なので、デフォルトでは有効になっていません。有効化するには、 設定パラメーター junit.jupiter.extensions.autodetection.enabledtrue を指定します。これは、JVMのシステムプロパティか、 Launcher に渡される LauncherDiscoveryRequest設定パラメーター、 あるいはJUnit Platform 設定ファイルで指定することができます (詳細は、設定パラメーター を参照)。

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

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

自動検出が有効化されると、ServiceLoader の仕組みで検知された拡張機能は、 JUnit Jupiterのグローバルな拡張機能 (例えば、TestInfoTestReporter のサポート) の後で拡張機能レジストリに登録されます。

5.2.4. 拡張機能の継承

登録された拡張機能は、テストクラス階層の中で親から子に継承されます。 同様に、クラスレベルで登録された拡張機能は、メソッドレベルに継承されます。 また、同一の拡張機能実装は、与えられた拡張コンテキストとその親の拡張コンテキストに対して 一回だけしか登録することができません。 結果として、重複して拡張機能実装を登録しようとしても無視されます。

5.3. 条件付きテスト実行

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

ExecutionCondition は、コンテナ (例えば、テストクラス) がもつテストを与えられた ExtensionContext で実行すべきか判断するためにコンテナ毎に評価されます。 同様に、ExecutionCondition は、個々のテストメソッドを与えられた ExtensionContext で実行すべきか判断するためにテスト毎に評価されます。

複数の ExecutionCondition 拡張が登録された場合、無効 と判定する拡張機能が1つでもあれば、 コンテナやテストはただちに無効化されます。別の拡張機能がすでにコンテナやテストを無効化していることがあるので、 ある拡張機能が必ず評価されるという保証はありません。言い換えると、論理OR演算子の短絡評価のように評価されます。

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

5.3.1. 条件を非アクティブにする

訳注:テストの条件 (ExecutionCondition) が成立し、該当テストが実行される状態を 有効、逆に実行されない状態を 無効 と訳している。これに対して、 テストの条件自体が評価される状態であることを アクティブ、 評価されない状態であることを 非アクティブ と区別して訳す。

ときには、特定の条件をアクティブにせずにテストスイートを実行できると有用な場合があります。 例えば、@Disabled アノテーションが付与されているテストであっても、今もまだ 壊れた ままかを確認するために実行したいと思うことがあるかもしれません。そのためには、 設定パラメーター junit.jupiter.conditions.deactivate を使って、 現在のテスト実行に対してどの条件を非アクティブ (つまり、評価されない) にするか パターンを指定するだけです。パターンは、JVMのシステムプロパティか、 Launcher に渡される LauncherDiscoveryRequest設定パラメーター、あるいはJUnit Platform 設定ファイルで指定することができます (詳細は、設定パラメーター を参照)。

例えば、JUnitの @Disabled 条件を非アクティブにするには、 JVMを次のシステムプロパティ付きで起動します。

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

パターンマッチング構文

junit.jupiter.conditions.deactivate のパターンがアスタリスク (*) のみからなる場合、すべての条件が非アクティブになります。一方、 登録された条件の完全修飾クラス名 (FQCN) に対するパターンマッチも使えます。 パターン中のドット (.) は、FQCN中のドット (.) またはドル記号 ($) にマッチします。 アスタリスク (*) は、FQCN中の1つ以上の文字にマッチします。 ほかのすべての文字は、FQCN中の文字に1対1でマッチします。

例:

  • *: すべての条件を非アクティブにします。

  • org.junit.*: org.junit パッケージおよびすべてのサブパッケージにある条件を非アクティブにします。

  • *.MyCondition: 単純クラス名が MyCondition である条件をすべて非アクティブにします。

  • *System*: 単純クラス名に System を含む条件をすべて非アクティブにします。

  • org.example.MyCondition: FQCNが org.example.MyCondition である条件を非アクティブにします。

5.4. テストインスタンスのファクトリ

TestInstanceFactory は、テストクラスのインスタンスを 生成する ための Extension APIを定めています。

一般的なユースケースとしては、DI(依存性注入)フレームワークからテストインスタンスを取得したり、 テストクラスのインスタンス生成にstaticファクトリメソッドを呼んだりすることです。

TestInstanceFactory が登録されていない場合は、 フレームワークは単にテストクラスの 唯一の コンストラクタを実行して初期化します。 このとき、コンストラクタの引数は、登録済みの ParameterResolver 拡張で解決されます。

TestInstanceFactory を実装する拡張機能は、テストインターフェースや トップレベルのテストクラス、あるいは @Nested テストクラスに対しても登録できます。

TestInstanceFactory を実装する複数の拡張機能を1つのクラスに登録すると、 そのクラスとサブクラス、およびネストするクラスのすべてのテストで例外がスローされます。 親クラスまたは 外部 クラス(つまり、@Nested テストクラスの場合)に登録された TestInstanceFactory継承される 点に注意してください。 テストクラスに TestInstanceFactory が1つだけしか登録されないようにするのは、 ユーザーの責任となります。

5.5. テストインスタンスの後処理

TestInstancePostProcessor は、テストインスタンスの 後処理 をするための Extension APIを定めています。

よくあるユースケースは、テストインスタンスへの依存性注入や、テストインスタンスのカスタムの 初期化メソッド呼び出しなどがあります。

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

5.6. 引数の解決

ParameterResolver は、実行時に動的に引数を解決するための Extension APIを定めています。

テストコンストラクタや @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll メソッドが引数をもつ場合、 その引数は ParameterResolver によって実行時に 解決 されなければいけません。 ParameterResolver は、ビルトインのもの (TestInfoParameterResolver 参照) または ユーザーが登録したもの のどちらかです。一般的に言うと、 引数は 名前アノテーション、あるいはその組み合わせで解決されます。 具体例は、CustomTypeParameterResolverCustomAnnotationParameterResolver のソースコードを参照してください。

JDK 9以前の javac が生成するバイトコードのバグのせいで、内部クラス のコンストラクタ (例えば、@Nested テストクラスのコンストラクタ) に対して引数のアノテーションを java.lang.reflect.Parameter API経由で直接探そうとすると、常に失敗します。

そのため、ParameterResolver 実装に与えられる ParameterContext APIは、 引数のアノテーションを正しく検索するための以下の便利メソッドを含んでいます。 拡張機能の作者は、JDKのバグを回避するために java.lang.reflect.Parameter ではなく、 これらのメソッドを使用することが強く推奨されます。

  • boolean isAnnotated(Class<? extends Annotation> annotationType)

  • Optional<A> findAnnotation(Class<A> annotationType)

  • List<A> findRepeatableAnnotations(Class<A> annotationType)

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

以下のインターフェースは、テスト実行のライフサイクルにおける様々なタイミングでテストを拡張するための APIを定めています。詳細は、以降の節にあるサンプルと、org.junit.jupiter.api.extension パッケージにある これらのインターフェースのJavadocを参照してください。

複数のExtension APIを実装する
拡張機能の開発者は、1つの拡張機能でこれらのインターフェースをいくつでも実装することができます。 具体例は、SpringExtension のソースコードを参照してください。

5.7.1. テスト実行前後のコールバック

BeforeTestExecutionCallbackAfterTestExecutionCallback は、 テストメソッドが実行される 直前直後 に実行されるような振る舞いを追加したい場合の Extension API を定めています。実行時間の計測や、処理のトレースなどのユースケースに適しています。 もしも @BeforeEach メソッドや @AfterEach メソッドの 前後 に呼び出されるコールバックが 必要な場合は、代わりに BeforeEachCallbackAfterEachCallback を実装してください。

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

テストメソッドの実行時間を計測してログ出力する拡張機能
import java.lang.reflect.Method;
import java.util.logging.Logger;

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

public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {

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

    private static final String START_TIME = "start time";

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

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

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

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

}

TimingExtensionTests クラスは @ExtendWith を使って TimingExtension を登録しているので、テストを実行すると時間計測が有効になります。

TimingExtensionを使うテストクラス
@ExtendWith(TimingExtension.class)
class TimingExtensionTests {

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

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

}

以下は、TimingExtensionTests が実行されたときに出力されるログの例です。

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

5.8. 例外ハンドリング

TestExecutionExceptionHandler は、テスト実行中に投げられた例外を扱うための Extension API を定めています。

次の例は、IOException のすべてのインスタンスをもみ消し、 それ以外の例外は再スローするような拡張機能を示しています。

例外をハンドリングする拡張機能
public class IgnoreIOExceptionExtension implements TestExecutionExceptionHandler {

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

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

5.9. テストテンプレートに実行コンテキストを与える

@TestTemplate メソッドは、TestTemplateInvocationContextProvider が少なくとも1つ登録されている場合のみ実行されます。これらのプロバイダは、 TestTemplateInvocationContext インスタンスの Stream を返す責任をもちます。 各コンテキストは、カスタム表示名と @TestTemplate メソッドの次の呼び出しだけで使われる 追加の拡張機能のリストを指定します。

次の例は、TestTemplateInvocationContextProvider を実装して登録する方法と、 テストテンプレートを記述する方法を示しています。

テストテンプレートと拡張機能
@TestTemplate
@ExtendWith(MyTestTemplateInvocationContextProvider.class)
void testTemplate(String parameter) {
    assertEquals(3, parameter.length());
}

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

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

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

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

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

この例では、テストテンプレートは2回実行されます。各実行の表示名は、実行コンテキストで指定された通り、 “foo” と “bar” になります。各実行では、メソッド引数を解決するために使われるカスタムの ParameterResolver を登録しています。 ConsoleLauncher を使ったときの出力は、次のようになります。

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

TestTemplateInvocationContextProvider 拡張APIは、 テストクラスのインスタンスを異なるコンテキスト(例えば、異なる引数)で準備したり、 同じコンテキストで何回も呼び出したりするような、メソッドを繰り返し実行する様々なテストを 実装することを主目的としています。 繰り返しテストパラメーター化テスト が、 その機能を実現するためにこの拡張ポイントをどのように使っているか参考にしてください。

5.10. 拡張機能の状態を保持する

通常、拡張機能は一度だけしかインスタンス化されません。そのため、 拡張機能のある実行から次の実行までどのように状態を保持するのか?が問題となります。 ExtensionContext APIは、まさにこの目的のために Store を提供しています。 拡張機能は、値を Store に保存しておいて、あとで取得することができます。 メソッドレベルのスコープで Store を使用する例については、 TimingExtension を参照してください。 テスト実行の間に ExtensionContext に保存された値は、その外側の ExtensionContext では利用できないことを覚えておくのは重要です。ExtensionContext はネストされうるので、 内側の拡張コンテキストのスコープも制限されることがあります。 Store 経由で保存・取得するために使えるメソッドの詳細については、 関連する JavaDoc を参照してください。

ExtensionContext.Store.CloseableResource
拡張コンテキストのストアは、その拡張コンテキストのライフサイクルに束縛されています。 拡張コンテキストのライフサイクルが終了すると、関連するストアもクローズされます。 保存されている値が CloseableResource のインスタンスである場合は、その close() メソッドが呼び出されて通知されます。

5.11. 拡張機能でサポートされるユーティリティ

junit-platform-commons は、アノテーションやクラス、リフレクション、 クラスパスの探索処理に使える メンテナンスされた ユーティリティメソッドを含む org.junit.platform.commons.support というパッケージを提供しています。 TestEngineExtension の作者は、JUnit Platformの振る舞いに合わせるために、 これらのメソッドを利用することが推奨されます。

5.11.1. アノテーションサポート

AnnotationSupport は、アノテーションが付与された要素(例えば、パッケージやアノテーション、 クラス、インターフェース、コンストラクタ、メソッド、フィールド)を操作するstatic なユーティリティメソッドを提供します。その中には、 ある要素に特定のアノテーションやメタアノテーションが付与されているかチェックしたり、 特定のアノテーションを探したり、クラスやフィールドからアノテーションの付与されたメソッドを 探したりするメソッドが含まれます。これらのメソッドのいくつかは、 アノテーションを探すために実装インターフェースやクラス階層を探索します。 さらなる詳細は、AnnotationSupport のJavaDocを確認してください。

5.11.2. クラスサポート

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

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

ReflectionSupport は、標準JDKのリフレクションとクラスロードの仕組みを強化する staticなユーティリティメソッドを提供します。 その中には、指定された述語にマッチするクラスを求めてクラスパスを探索したり、 あるクラスをロードして新しいインスタンスを作成したり、メソッドを探して実行したりする メソッドが含まれます。 これらのメソッドのいくつかは、マッチするメソッドを特定するためにクラス階層を探索します。 さらなる詳細は、ReflectionSupport のJavaDocを確認してください。

5.12. ユーザーコードと拡張機能の相対的な実行順序

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

extensions lifecycle
ユーザーコードと拡張機能コード

ユーザーが提供するテストメソッドとライフサイクルメソッドはオレンジ色、 拡張機能が提供するコールバックコードは青色で示されています。灰色のボックスは、 単一のテストメソッドの実行を表し、テストクラス内のテストメソッド毎に繰り返されます。

次の表は、ユーザーコードと拡張機能コード の図の12ステップをさらに説明しています。

ステップ インターフェース/アノテーション 説明

1

org.junit.jupiter.api.extension.BeforeAllCallback インターフェース

コンテナのすべてのテストが実行される前に実行される拡張機能のコード

2

org.junit.jupiter.api.BeforeAll アノテーション

コンテナのすべてのテストが実行される前に実行されるユーザーコード

3

org.junit.jupiter.api.extension.BeforeEachCallback インターフェース

各テストが実行される前に実行される拡張機能のコード

4

org.junit.jupiter.api.BeforeEach アノテーション

各テストが実行される前に実行されるユーザーコード

5

org.junit.jupiter.api.extension.BeforeTestExecutionCallback インターフェース

テストが実行される直前に実行される拡張機能のコード

6

org.junit.jupiter.api.Test アノテーション

実際のテストメソッドとなるユーザーコード

7

org.junit.jupiter.api.extension.TestExecutionExceptionHandler インターフェース

テストの間に投げられた例外をハンドリングするための拡張機能のコード

8

org.junit.jupiter.api.extension.AfterTestExecutionCallback インターフェース

テストが実行され、その例外ハンドラが処理された直後に実行される拡張機能のコード

9

org.junit.jupiter.api.AfterEach アノテーション

各テストが実行された後に実行されるユーザーコード

10

org.junit.jupiter.api.extension.AfterEachCallback インターフェース

各テストが実行された後に実行される拡張機能のコード

11

org.junit.jupiter.api.AfterAll アノテーション

コンテナのすべてのテストが実行された後に実行されるユーザーコード

12

org.junit.jupiter.api.extension.AfterAllCallback インターフェース

コンテナのすべてのテストが実行された後に実行される拡張機能のコード

最も単純な場合は、実際のテストメソッドだけが実行されます (ステップ 6)。 ほかのすべてのステップは、ライフサイクルコールバックに対するユーザーコードや拡張機能コードの 有無によって省略可能です。様々なライフサイクルコールバックに関する詳細については、 各アノテーションや拡張機能に対する JavaDoc を参照してください。

6. JUnit 4からの移行

JUnit Jupiterのプログラミングモデルと拡張モデルは、RuleRunner のようなJUnit 4の機能をネイティブにはサポートしていません。しかし、 ソースコードのメンテナがJUnit Jupiterへ移行するためにすべての既存テストやテスト拡張、 カスタムビルド基盤を書き換えることを求めているわけではありません。

代わりに、JUnit Platform上でJUnit 3またはJUnit 4ベースの既存テストを実行できる ようにする JUnit Vintageテストエンジン を通して、なだらかな移行パスを提供しています。 JUnit Jupiter固有のクラスやアノテーションはすべて新設された org.junit.jupiter パッケージ配下にあるため、JUnit 4とJUnit Jupiterが同時にクラスパスに含まれていても コンフリクトは発生しません。そのため、既存のJUnit 4テストをJUnit Jupiter テストと並行してメンテナンスしても安全です。さらに、JUnitチームはJUnit 4.x 系のメンテナンスとバグフィックスリリースを継続しているため、 開発者は自身のスケジュールに合わせてJUnit Jupiterに移行する十分な時間があります。

6.1. JUnit Platform上でJUnit 4テストを実行する

junit-vintage-engine がテストランタイムパスに含まれていることを確認してください。 そうすれば、JUnit Platformランチャーが、JUnit 3およびJUnit 4のテストを自動的に ピックアップしてくれます。

junit5-samples リポジトリにあるサンプルプロジェクトを 参照して、GradleやMavenでどのように実現するか確認してください。

6.1.1. カテゴリのサポート

@Category アノテーションの付与されたテストクラスやテストメソッドに対しては、 JUnit Vintageテストエンジン はカテゴリの完全修飾クラス名を該当テストのタグとみなします。 例えば、テストメソッドに @Category(Example.class) アノテーションが付与されている場合、 "com.acme.Example" でタグ付けされます。JUnit 4の Categories ランナーと同様に、 この情報は実行するテストのフィルタリングに使われます (詳細は、テストを実行する 参照)。

6.2. 移行のヒント

既存のJUnit 4テストをJUnit Jupiterに移行するときに気をつけなければいけないことは、 次の通りです。

  • アノテーションは、org.junit.jupiter.api パッケージにあります。

  • アサーションは、org.junit.jupiter.api.Assertions にあります。

  • 前提条件は、org.junit.jupiter.api.Assumptions にあります。

  • @Before@After は、なくなりました。代わりに、@BeforeEach@AfterEach を使ってください。

  • @BeforeClass@AfterClass は、なくなりました。代わりに、@BeforeAll@AfterAll を使ってください。

  • @Ignore は、なくなりました。代わりに、@Disabled を使ってください。

  • @Category は、なくなりました。代わりに、@Tag を使ってください。

  • @RunWith は、なくなりました。@ExtendWith に置き換えられました。

  • @Rule@ClassRule は、なくなりました。@ExtendWith に置き換えられました。 部分的なルールのサポートについては、次節を参照してください。

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

前述したように、JUnit JupiterはJUnit 4のルールをサポートしていませんし、 その予定もありません。しかし、JUnitチームは、多くの組織が(特に大きな組織ほど) カスタムルールを使ったJUnit 4のコードベースを保持していることを理解しています。 このような組織に段階的な移行パスを用意するため、JUnitチームはJUnit Jupiter でJUnit 4のルールをサポートすることを決めました。 このサポートはアダプタをベースにしており、JUnit Jupiterの拡張モデルと意味的に 互換性があるルールに限定されています。 例えば、テストの実行フローを完全に変えてしまうようなことがないものです。

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

  • org.junit.rules.ExternalResource (org.junit.rules.TemporaryFolder も含む)

  • org.junit.rules.Verifier (org.junit.rules.ErrorCollector も含む)

  • org.junit.rules.ExpectedException

JUnit 4と同様に、Ruleアノテーションが付与されたフィールドとメソッドをサポートします。 これらのクラスレベルの拡張機能を使うことで、レガシーコードがもつ Rule 実装をJUnit 4のimport文も含めて 変更せずにそのままにしておく ことができます。

この限定的な形での Rule サポートは、クラスレベルのアノテーション org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport を使って有効にすることができます。このアノテーションは、 VerifierSupport, ExternalResourceSupport, ExpectedExceptionSupport のすべての移行サポート拡張を有効にするための 合成アノテーション になっています。

なお、JUnit 5に対する新しい拡張機能を開発するときには、JUnit 4 のルールベースのモデルではなく、JUnit Jupiterの拡張モデルを使ってください。

JUnit JupiterにおけるJUnit 4の Rule サポートは、今のところ 実験的な 機能です。詳細は、実験的なAPI の表を確認してください。

7. Advanced Topics

7.1. JUnit Platform Launcher API

One of the prominent goals of JUnit 5 is to make the interface between JUnit and its programmatic clients – build tools and IDEs – more powerful and stable. The purpose is to decouple the internals of discovering and executing tests from all the filtering and configuration that’s necessary from the outside.

JUnit 5 introduces the concept of a Launcher that can be used to discover, filter, and execute tests. Moreover, third party test libraries – like Spock, Cucumber, and FitNesse – can plug into the JUnit Platform’s launching infrastructure by providing a custom TestEngine.

The launcher API is in the junit-platform-launcher module.

An example consumer of the launcher API is the ConsoleLauncher in the junit-platform-console project.

7.1.1. Discovering Tests

Introducing test discovery as a dedicated feature of the platform itself will (hopefully) free IDEs and build tools from most of the difficulties they had to go through to identify test classes and test methods in the past.

Usage Example:

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

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

Launcher launcher = LauncherFactory.create();

TestPlan testPlan = launcher.discover(request);

There’s currently the possibility to select classes, methods, and all classes in a package or even search for all tests in the classpath. Discovery takes place across all participating test engines.

The resulting TestPlan is a hierarchical (and read-only) description of all engines, classes, and test methods that fit the LauncherDiscoveryRequest. The client can traverse the tree, retrieve details about a node, and get a link to the original source (like class, method, or file position). Every node in the test plan has a unique ID that can be used to invoke a particular test or group of tests.

7.1.2. Executing Tests

To execute tests, clients can use the same LauncherDiscoveryRequest as in the discovery phase or create a new request. Test progress and reporting can be achieved by registering one or more TestExecutionListener implementations with the Launcher as in the following example.

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

Launcher launcher = LauncherFactory.create();

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

launcher.execute(request);

There is no return value for the execute() method, but you can easily use a listener to aggregate the final results in an object of your own. For an example see the SummaryGeneratingListener.

7.1.3. Plugging in your own Test Engine

JUnit currently provides two TestEngine implementations.

Third parties may also contribute their own TestEngine by implementing the interfaces in the junit-platform-engine module and registering their engine. By default, engine registration is supported via Java’s java.util.ServiceLoader mechanism. For example, the junit-jupiter-engine module registers its org.junit.jupiter.engine.JupiterTestEngine in a file named org.junit.platform.engine.TestEngine within the /META-INF/services in the junit-jupiter-engine JAR.

HierarchicalTestEngine is a convenient abstract base implementation (used by the junit-jupiter-engine) that only requires implementors to provide the logic for test discovery. It implements execution of TestDescriptors that implement the Node interface, including support for parallel execution.
The junit- prefix is reserved for TestEngines from the JUnit Team

The JUnit Platform Launcher enforces that only TestEngine implementations published by the JUnit Team may use the junit- prefix for their TestEngine IDs.

  • If any third-party TestEngine claims to be junit-jupiter or junit-vintage, an exception will be thrown, immediately halting execution of the JUnit Platform.

  • If any third-party TestEngine uses the junit- prefix for its ID, a warning message will be logged. Later releases of the JUnit Platform will throw an exception for such violations.

7.1.4. Plugging in your own Test Execution Listener

In addition to the public Launcher API method for registering test execution listeners programmatically, by default custom TestExecutionListener implementations will be discovered at runtime via Java’s java.util.ServiceLoader mechanism and automatically registered with the Launcher created via the LauncherFactory. For example, an example.TestInfoPrinter class implementing TestExecutionListener and declared within the /META-INF/services/org.junit.platform.launcher.TestExecutionListener file is loaded and registered automatically.

7.1.5. Configuring the Launcher

If you require fine-grained control over automatic detection and registration of test engines and test execution listeners, you may create an instance of LauncherConfig and supply that to the LauncherFactory.create(LauncherConfig) method. Typically an instance of LauncherConfig is created via the built-in fluent builder API, as demonstrated in the following example.

LauncherConfig launcherConfig = LauncherConfig.builder()
    .enableTestEngineAutoRegistration(false)
    .enableTestExecutionListenerAutoRegistration(false)
    .addTestEngines(new CustomTestEngine())
    .addTestExecutionListeners(new CustomTestExecutionListener())
    .build();

Launcher launcher = LauncherFactory.create(launcherConfig);

LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
    .selectors(selectPackage("com.example.mytests"))
    .build();

launcher.execute(request);

8. APIの進化

JUnit 5の主要な目的の1つは、JUnitが多くのプロジェクトで使われていたとしても、 メンテナがJUnitを進化させられるようにすることです。 JUnit 4では多くの部品が元々は内部用途で追加されましたが、 外部の拡張機能やツールの作者から使われることになりました。 そのせいで、JUnit 4の変更はとりわけ難しく、ときには不可能になりました。

JUnit 5がすべての公開インターフェースやクラス、メソッドに対して明確に定義された ライフサイクルを導入したのはこのためです。

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

公開されているモジュールは <major>.<minor>.<patch> というバージョン番号をもち、 すべての公開インターフェース、クラス、メソッドには @API Guardian プロジェクトの @API アノテーションが付与されています。このアノテーションの status 属性には、 次のいずれかの値が設定されています。

ステータス 説明

INTERNAL

JUnit自身以外のコードで使ってはならない。事前予告なしに削除されうる。

DEPRECATED

もう使うべきではない。次のマイナー (minor) リリースで削除されうる。

EXPERIMENTAL

フィードバックがほしい実験的な新機能を意味する。注意して使うべき。
将来 MAINTAINED または STABLE に昇格される可能性がある一方で、 パッチ (patch) リリースでも事前予告なしに削除されうる。

MAINTAINED

現在のメジャー (major) バージョンの 少なくとも 次のマイナー (minor) リリースでは、後方互換性がない方法で変更されることがない機能を意味する。 削除予定になった場合は、まず最初に DEPRECATED に降格される。

STABLE

現在のメジャーバージョン (5.*) の間は、 後方互換性がない方法で変更されることがない機能を意味する。

@API アノテーションが型に付与された場合、その型のすべての公開メンバーに適用されているとみなします。 個々のメンバーは、より低い安定度をもつ異なる status 属性を宣言することができます。

8.2. 実験的なAPI

次の表は、@API(status = EXPERIMENTAL) で現在 実験的 と指定されたAPIを一覧しています。 これらのAPIを使う場合は注意してください。

Package Name Type Name Since

org.junit.jupiter.api

AssertionsKt (class)

5.1

org.junit.jupiter.api.condition

DisabledIf (annotation)

5.1

org.junit.jupiter.api.condition

EnabledIf (annotation)

5.1

org.junit.jupiter.api.extension

ScriptEvaluationException (class)

5.1

org.junit.jupiter.api.extension

TestInstanceFactory (interface)

5.3

org.junit.jupiter.api.extension

TestInstanceFactoryContext (interface)

5.3

org.junit.jupiter.api.extension

TestInstantiationException (class)

5.3

org.junit.jupiter.api.parallel

Execution (annotation)

5.3

org.junit.jupiter.api.parallel

ExecutionMode (enum)

5.3

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

ParameterizedTest (annotation)

5.0

org.junit.jupiter.params.aggregator

AggregateWith (annotation)

5.2

org.junit.jupiter.params.aggregator

ArgumentAccessException (class)

5.2

org.junit.jupiter.params.aggregator

ArgumentsAccessor (interface)

5.2

org.junit.jupiter.params.aggregator

ArgumentsAggregationException (class)

5.2

org.junit.jupiter.params.aggregator

ArgumentsAggregator (interface)

5.2

org.junit.jupiter.params.converter

ArgumentConversionException (class)

5.0

org.junit.jupiter.params.converter

ArgumentConverter (interface)

5.0

org.junit.jupiter.params.converter

ConvertWith (annotation)

5.0

org.junit.jupiter.params.converter

JavaTimeConversionPattern (annotation)

5.0

org.junit.jupiter.params.converter

SimpleArgumentConverter (class)

5.0

org.junit.jupiter.params.provider

Arguments (interface)

5.0

org.junit.jupiter.params.provider

ArgumentsProvider (interface)

5.0

org.junit.jupiter.params.provider

ArgumentsSource (annotation)

5.0

org.junit.jupiter.params.provider

ArgumentsSources (annotation)

5.0

org.junit.jupiter.params.provider

CsvFileSource (annotation)

5.0

org.junit.jupiter.params.provider

CsvParsingException (class)

5.3

org.junit.jupiter.params.provider

CsvSource (annotation)

5.0

org.junit.jupiter.params.provider

EnumSource (annotation)

5.0

org.junit.jupiter.params.provider

MethodSource (annotation)

5.0

org.junit.jupiter.params.provider

ValueSource (annotation)

5.0

org.junit.jupiter.params.support

AnnotationConsumer (interface)

5.0

org.junit.platform.engine.support.config

PrefixedConfigurationParameters (class)

1.3

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

ExecutionMode (enum)

1.3

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

LauncherConstants (class)

1.3

org.junit.platform.launcher.core

LauncherConfig (interface)

1.3

8.3. 非推奨のAPI

次の表は、@API(status = DEPRECATED) で現在 非推奨 と指定されたAPIを一覧しています。 非推奨になったAPIは今後のリリースで削除される可能性が高いため、可能な限り使わないようにしてください。

Package Name Type Name Since

org.junit.platform.engine.support.hierarchical

SingleTestExecutor (class)

1.2

org.junit.platform.surefire.provider

JUnitPlatformProvider (class)

1.3

8.4. @APIツールサポート

@API Guardian プロジェクトは、 @API アノテーションが付与されたAPIの提供者や利用者のために、 ツールによるサポートを計画しています。例えば、JUnitのAPIが @API アノテーションの宣言にしたがって使われているかをチェックする手段を提供するなどです。

9. コントリビューター

GitHubで直接 現在のコントリビューター を参照してください。

10. リリースノート

リリースノートは、 こちら から参照できます。