1. 概要

このドキュメントの目的は、テストを記述するプログラマー、拡張機能の作成者、エンジン作成者、およびビルドツールと IDE ベンダー向けに包括的なリファレンスドキュメントを提供することです。

このドキュメントは、PDF ダウンロードとしても入手できます。

1.1. JUnit 5 とは?

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

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform は、JVM でのテストフレームワークの起動の基盤として機能します。また、プラットフォーム上で実行されるテストフレームワークを開発するためのTestEngine API も定義しています。さらに、プラットフォームは、コマンドラインからプラットフォームを起動するためのコンソールランチャーと、プラットフォーム上の 1 つ以上のテストエンジンを使用してカスタムテストスイートを実行するためのJUnit Platform スイートエンジンを提供しています。JUnit Platform のファーストクラスサポートは、一般的な IDE ( IntelliJ IDEAEclipseNetBeansVisual Studio Codeを参照) およびビルドツール ( GradleMaven 、およびAntを参照) でも利用できます。

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

JUnit Vintage は、プラットフォーム上で JUnit 3 および JUnit 4 ベースのテストを実行するための TestEngine を提供します。クラスパスまたはモジュールパスに JUnit 4.12 以降が存在する必要があります。

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

JUnit 5 では、実行時に Java 8 (またはそれ以降) が必要です。ただし、以前のバージョンの JDK でコンパイルされたコードをテストすることもできます。

1.3. ヘルプの入手

Stack Overflow で JUnit 5 関連の質問をするか、Gitter でコミュニティとチャットしてください。

1.4. はじめに

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

ダウンロードしてプロジェクトに含めることができるアーティファクトを確認するには、依存関係メタデータを参照してください。ビルドの依存関係管理を設定するには、ビルドサポートサンプルプロジェクトを参照してください。

1.4.2. JUnit 5 の機能

JUnit 5 で利用できる機能とその使用方法については、トピック別に整理されたこのユーザーガイドの対応するセクションを参照してください。

1.4.3. サンプルプロジェクト

コピーして実験できるプロジェクトの完全な実例を確認するには、junit5-samples リポジトリから始めるのが良いでしょう。junit5-samples リポジトリは、JUnit Jupiter、JUnit Vintage、およびその他のテストフレームワークに基づいたサンプルプロジェクトのコレクションをホストしています。サンプルプロジェクトには、適切なビルドスクリプト (例: build.gradlepom.xml など) があります。以下のリンクは、選択できる組み合わせの一部を強調表示しています。

2. テストの記述

次の例は、JUnit Jupiter でテストを記述するための最小限の要件を示しています。この章の後のセクションでは、利用可能なすべての機能について詳しく説明します。

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

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class MyFirstJUnitJupiterTests {

    private final Calculator calculator = new Calculator();

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

}

2.1. アノテーション

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

特に明記されていない限り、すべてのコアアノテーションは、junit-jupiter-api モジュールの org.junit.jupiter.api パッケージにあります。

アノテーション 説明

@Test

メソッドがテストメソッドであることを示します。JUnit 4 の @Test アノテーションとは異なり、このアノテーションは属性を宣言しません。JUnit Jupiter のテスト拡張機能は、専用のアノテーションに基づいて動作するためです。このようなメソッドは、オーバーライドされない限り、継承されます。

@ParameterizedTest

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

@RepeatedTest

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

@TestFactory

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

@TestTemplate

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

@TestClassOrder

アノテーション付きのテストクラス内の @Nested テストクラスのテストクラスの実行順序を設定するために使用されます。このようなアノテーションは継承されます。

@TestMethodOrder

アノテーション付きのテストクラスのテストメソッドの実行順序を設定するために使用されます。JUnit 4 の @FixMethodOrder と同様です。このようなアノテーションは継承されます。

@TestInstance

アノテーション付きのテストクラスのテストインスタンスのライフサイクルを設定するために使用されます。このようなアノテーションは継承されます。

@DisplayName

テストクラスまたはテストメソッドのカスタム表示名を宣言します。このようなアノテーションは継承されません。

@DisplayNameGeneration

テストクラスのカスタム表示名ジェネレーターを宣言します。このようなアノテーションは継承されます。

@BeforeEach

アノテーション付きのメソッドは、現在のクラスの @Test@RepeatedTest@ParameterizedTest、または @TestFactory メソッドの に実行されるべきであることを示します。JUnit 4 の @Before と同様です。このようなメソッドは、オーバーライドまたは置き換え (つまり、Java の可視性ルールに関係なく、シグネチャのみに基づいて置き換えられる) されない限り、継承されます。

@AfterEach

アノテーション付きのメソッドは、現在のクラスの @Test@RepeatedTest@ParameterizedTest、または @TestFactory メソッドの に実行されるべきであることを示します。JUnit 4 の @After と同様です。このようなメソッドは、オーバーライドまたは置き換え (つまり、Java の可視性ルールに関係なく、シグネチャのみに基づいて置き換えられる) されない限り、継承されます。

@BeforeAll

アノテーション付きのメソッドは、現在のクラスの すべて@Test@RepeatedTest@ParameterizedTest、および @TestFactory メソッドの に実行されるべきであることを示します。JUnit 4 の @BeforeClass と同様です。このようなメソッドは、非表示オーバーライド、または置き換え (つまり、Java の可視性ルールに関係なく、シグネチャのみに基づいて置き換えられる) されない限り、継承されます。また、「クラスごと」のテストインスタンスのライフサイクルが使用されない限り、static である必要があります。

@AfterAll

アノテーション付きのメソッドは、現在のクラスの すべて@Test@RepeatedTest@ParameterizedTest、および @TestFactory メソッドの に実行されるべきであることを示します。JUnit 4 の @AfterClass と同様です。このようなメソッドは、非表示オーバーライド、または置き換え (つまり、Java の可視性ルールに関係なく、シグネチャのみに基づいて置き換えられる) されない限り、継承されます。また、「クラスごと」のテストインスタンスのライフサイクルが使用されない限り、static である必要があります。

@Nested

アノテーション付きのクラスが、非静的なネストされたテストクラスであることを示します。Java 8 から Java 15 では、「クラスごと」のテストインスタンスのライフサイクルが使用されていない限り、@BeforeAll および @AfterAll メソッドを @Nested テストクラスで直接使用することはできません。Java 16 以降では、@BeforeAll および @AfterAll メソッドを、どちらのテストインスタンスライフサイクルモードでも、@Nested テストクラスで static として宣言できます。このようなアノテーションは継承されません。

@Tag

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

@Disabled

テストクラスまたはテストメソッドを無効にするために使用されます。JUnit 4 の @Ignore と同様です。このようなアノテーションは継承されません。

@Timeout

テスト、テストファクトリー、テストテンプレート、またはライフサイクルメソッドの実行が指定された時間を超えた場合に、失敗させるために使用されます。このようなアノテーションは継承されます。

@ExtendWith

拡張機能を宣言的に登録するために使用されます。このようなアノテーションは継承されます。

@RegisterExtension

フィールドを介して拡張機能をプログラムで登録するために使用されます。このようなフィールドは、シャドウイングされない限り、継承されます。

@TempDir

ライフサイクルメソッドまたはテストメソッドで、フィールドインジェクションまたはパラメータインジェクションを介して一時ディレクトリを提供するために使用されます。org.junit.jupiter.api.io パッケージにあります。

一部のアノテーションは現在、実験的である可能性があります。詳細については、実験的 API の表を参照してください。

2.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 {
}

次の @Test メソッドは、@Fast アノテーションの使用法を示しています。

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

さらに一歩進めて、@Tag("fast") *と* @Test のドロップイン代替として使用できるカスタムの @FastTest アノテーションを導入することもできます。

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

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

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

JUnit は、次のアノテーションを "fast" でタグ付けされた @Test メソッドとして自動的に認識します。

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

2.2. 定義

プラットフォームの概念
コンテナ

他のコンテナまたはテストを子として含むテストツリー内のノード (例: *テストクラス*)。

テスト

実行時に期待される動作を検証するテストツリー内のノード (例: @Test メソッド)。

Jupiter の概念
ライフサイクルメソッド

@BeforeAll@AfterAll@BeforeEach、または @AfterEach で直接アノテーション付けされているか、メタアノテーション付けされているメソッド。

テストクラス

少なくとも 1 つの*テストメソッド*を含む最上位クラス、static メンバークラス、または@Nested クラス。つまり、コンテナです。テストクラスは abstract にすることはできず、単一のコンストラクタを持つ必要があります。

テストメソッド

@Test@RepeatedTest@ParameterizedTest@TestFactory、または @TestTemplate で直接アノテーション付けされているか、メタアノテーション付けされているインスタンスメソッド。@Test を除き、これらはテストツリーに テスト をグループ化する コンテナ、または (@TestFactory の場合) 他の コンテナ を作成します。

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

テストメソッドとライフサイクルメソッドは、現在のテストクラス内でローカルに宣言することも、スーパークラスから継承することも、インターフェイスから継承することもできます(テストインターフェイスとデフォルトメソッドを参照)。さらに、テストメソッドとライフサイクルメソッドは abstract にすることはできず、値を返してはなりません (値を返す必要がある @TestFactory メソッドを除く)。

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

テストクラス、テストメソッド、およびライフサイクルメソッドは public である必要はありませんが、private にはできません

一般的に、テストクラスが別のパッケージのテストクラスによって拡張されている場合など、技術的な理由がない限り、テストクラス、テストメソッド、およびライフサイクルメソッドの public 修飾子を省略することをお勧めします。クラスとメソッドを public にするもう 1 つの技術的な理由は、Java モジュールシステムを使用する場合にモジュールパスでのテストを簡素化することです。

次のテストクラスは、@Test メソッドとサポートされているすべてのライフサイクルメソッドの使用法を示しています。ランタイムセマンティクスの詳細については、テストの実行順序コールバックのラッピング動作を参照してください。

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

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

class StandardTests {

    @BeforeAll
    static void initAll() {
    }

    @BeforeEach
    void init() {
    }

    @Test
    void succeedingTest() {
    }

    @Test
    void failingTest() {
        fail("a failing test");
    }

    @Test
    @Disabled("for demonstration purposes")
    void skippedTest() {
        // not executed
    }

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

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}

2.4. 表示名

テストクラスとテストメソッドは、テストレポート、テストランナー、および IDE に表示される、スペース、特殊文字、さらには絵文字を含むカスタム表示名を @DisplayName を介して宣言できます。

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

@DisplayName("A special test case")
class DisplayNameDemo {

    @Test
    @DisplayName("Custom test name containing spaces")
    void testWithDisplayNameContainingSpaces() {
    }

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

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

}

2.4.1. 表示名ジェネレーター

JUnit Jupiter は、@DisplayNameGeneration アノテーションを介して設定できるカスタム表示名ジェネレーターをサポートしています。@DisplayName アノテーションを介して提供された値は、常に DisplayNameGenerator によって生成された表示名よりも優先されます。

ジェネレーターは DisplayNameGenerator を実装することで作成できます。以下は、Jupiter で利用できるデフォルトのジェネレーターの一部です

DisplayNameGenerator 動作

Standard

JUnit Jupiter 5.0 がリリースされて以来、標準の表示名生成動作に一致します。

Simple

パラメータのないメソッドの末尾の括弧を削除します。

ReplaceUnderscores

アンダースコアをスペースに置き換えます。

IndicativeSentences

テストの名前と囲みクラスの名前を連結して、完全な文を生成します。

IndicativeSentences の場合、次の例に示すように、@IndicativeSentencesGeneration を使用して、区切り文字と基になるジェネレーターをカスタマイズできることに注意してください。

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

class DisplayNameGeneratorDemo {

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

        @Test
        void if_it_is_zero() {
        }

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

    }

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

        @Test
        void if_it_is_divisible_by_4_but_not_by_100() {
        }

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

    }

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

2.4.2. デフォルトの表示名ジェネレーターの設定

junit.jupiter.displayname.generator.default 構成パラメータを使用して、デフォルトで使用する DisplayNameGenerator の完全修飾クラス名を指定できます。@DisplayNameGeneration アノテーションを介して構成された表示名ジェネレーターと同様に、指定されたクラスは DisplayNameGenerator インターフェイスを実装する必要があります。デフォルトの表示名ジェネレーターは、囲みテストクラスまたはテストインターフェイスに @DisplayNameGeneration アノテーションが存在しない限り、すべてのテストに使用されます。@DisplayName アノテーションを介して提供された値は、常に DisplayNameGenerator によって生成された表示名よりも優先されます。

たとえば、デフォルトで ReplaceUnderscores 表示名ジェネレーターを使用するには、構成パラメータを対応する完全修飾クラス名 (例: src/test/resources/junit-platform.properties 内) に設定する必要があります。

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

同様に、DisplayNameGenerator を実装するカスタムクラスの完全修飾名を指定できます。

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

  1. @DisplayName アノテーションの値(存在する場合)

  2. @DisplayNameGeneration アノテーションで指定された DisplayNameGenerator を呼び出す(存在する場合)

  3. 構成パラメータで構成されたデフォルトの DisplayNameGenerator を呼び出す(存在する場合)

  4. org.junit.jupiter.api.DisplayNameGenerator.Standard を呼び出す

2.5. アサーション

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 java.util.concurrent.CountDownLatch;

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

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

class AssertionsDemo {

    private final Calculator calculator = new Calculator();

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

    @Test
    void standardAssertions() {
        assertEquals(2, calculator.add(1, 1));
        assertEquals(4, calculator.multiply(2, 2),
                "The optional failure message is now the last parameter");
        assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- "
                + "to avoid constructing complex messages unnecessarily.");
    }

    @Test
    void groupedAssertions() {
        // In a grouped assertion all assertions are executed, and all
        // failures will be reported together.
        assertAll("person",
            () -> assertEquals("Jane", person.getFirstName()),
            () -> assertEquals("Doe", person.getLastName())
        );
    }

    @Test
    void dependentAssertions() {
        // Within a code block, if an assertion fails the
        // subsequent code in the same block will be skipped.
        assertAll("properties",
            () -> {
                String firstName = person.getFirstName();
                assertNotNull(firstName);

                // Executed only if the previous assertion is valid.
                assertAll("first name",
                    () -> assertTrue(firstName.startsWith("J")),
                    () -> assertTrue(firstName.endsWith("e"))
                );
            },
            () -> {
                // Grouped assertion, so processed independently
                // of results of first name assertions.
                String lastName = person.getLastName();
                assertNotNull(lastName);

                // Executed only if the previous assertion is valid.
                assertAll("last name",
                    () -> assertTrue(lastName.startsWith("D")),
                    () -> assertTrue(lastName.endsWith("e"))
                );
            }
        );
    }

    @Test
    void exceptionTesting() {
        Exception exception = assertThrows(ArithmeticException.class, () ->
            calculator.divide(1, 0));
        assertEquals("/ by zero", exception.getMessage());
    }

    @Test
    void timeoutNotExceeded() {
        // The following assertion succeeds.
        assertTimeout(ofMinutes(2), () -> {
            // Perform task that takes less than 2 minutes.
        });
    }

    @Test
    void timeoutNotExceededWithResult() {
        // The following assertion succeeds, and returns the supplied object.
        String actualResult = assertTimeout(ofMinutes(2), () -> {
            return "a result";
        });
        assertEquals("a result", actualResult);
    }

    @Test
    void timeoutNotExceededWithMethod() {
        // The following assertion invokes a method reference and returns an object.
        String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting);
        assertEquals("Hello, World!", actualGreeting);
    }

    @Test
    void timeoutExceeded() {
        // The following assertion fails with an error message similar to:
        // execution exceeded timeout of 10 ms by 91 ms
        assertTimeout(ofMillis(10), () -> {
            // Simulate task that takes more than 10 ms.
            Thread.sleep(100);
        });
    }

    @Test
    void timeoutExceededWithPreemptiveTermination() {
        // The following assertion fails with an error message similar to:
        // execution timed out after 10 ms
        assertTimeoutPreemptively(ofMillis(10), () -> {
            // Simulate task that takes more than 10 ms.
            new CountDownLatch(1).await();
        });
    }

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

}
assertTimeoutPreemptively() を使用したプリエンプティブタイムアウト

Assertions クラスのさまざまな assertTimeoutPreemptively() メソッドは、提供された executable または supplier を、呼び出し元のコードとは異なるスレッドで実行します。この動作は、executable または supplier 内で実行されるコードが java.lang.ThreadLocal ストレージに依存している場合、望ましくない副作用につながる可能性があります。

この一般的な例の1つは、Spring Frameworkのトランザクションテストサポートです。具体的には、Springのテストサポートは、テストメソッドが呼び出される前に、トランザクションの状態を現在のスレッド(ThreadLocal経由)にバインドします。その結果、assertTimeoutPreemptively() に提供される executable または supplier がトランザクションに参加するSpring管理のコンポーネントを呼び出す場合、それらのコンポーネントによって実行されたアクションは、テスト管理のトランザクションでロールバックされません。それどころか、そのようなアクションは、テスト管理のトランザクションがロールバックされたとしても、永続ストア(例:リレーショナルデータベース)にコミットされます。

同様の副作用は、ThreadLocal ストレージに依存する他のフレームワークでも発生する可能性があります。

2.5.1. Kotlin アサーションサポート

JUnit Jupiterには、Kotlinでの使用に適したアサーションメソッドもいくつか付属しています。すべてのJUnit Jupiter Kotlinアサーションは、org.junit.jupiter.apiパッケージのトップレベル関数です。

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

class KotlinAssertionsDemo {

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

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

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

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

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

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

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

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

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

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

たとえば、マッチャーとFluent APIの組み合わせを使用して、アサーションをより記述的で読みやすくすることができます。ただし、JUnit Jupiterのorg.junit.jupiter.api.Assertionsクラスは、JUnit 4のorg.junit.AssertクラスにあるようなHamcrestのMatcherを受け入れるassertThat()メソッドを提供していません。代わりに、開発者はサードパーティのアサーションライブラリが提供する組み込みのマッチャーサポートを使用することを推奨します。

次の例は、JUnit JupiterテストでHamcrestのassertThat()サポートを使用する方法を示しています。Hamcrestライブラリがクラスパスに追加されている限り、assertThat()is()equalTo()などのメソッドを静的にインポートして、以下のassertWithHamcrestMatcher()メソッドのようにテストで使用できます。

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

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class HamcrestAssertionsDemo {

    private final Calculator calculator = new Calculator();

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

}

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

2.6. 仮定

JUnit Jupiterには、JUnit 4が提供する仮定メソッドのサブセットが含まれており、Java 8ラムダ式とメソッド参照で使用するのに適したものがいくつか追加されています。すべてのJUnit Jupiterの仮定は、org.junit.jupiter.api.Assumptionsクラスの静的メソッドです。

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

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class AssumptionsDemo {

    private final Calculator calculator = new Calculator();

    @Test
    void testOnlyOnCiServer() {
        assumeTrue("CI".equals(System.getenv("ENV")));
        // remainder of test
    }

    @Test
    void testOnlyOnDeveloperWorkstation() {
        assumeTrue("DEV".equals(System.getenv("ENV")),
            () -> "Aborting test: not on developer workstation");
        // remainder of test
    }

    @Test
    void testInAllEnvironments() {
        assumingThat("CI".equals(System.getenv("ENV")),
            () -> {
                // perform these assertions only on the CI server
                assertEquals(2, calculator.divide(4, 2));
            });

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

}
JUnit Jupiter 5.4以降では、JUnit 4のorg.junit.Assumeクラスのメソッドを仮定に使用することもできます。具体的には、JUnit Jupiterは、テストが失敗としてマークされるのではなく、中止されるべきであることを示すために、JUnit 4のAssumptionViolatedExceptionをサポートしています。

2.7. テストの無効化

テストクラス全体または個々のテストメソッドは、@Disabledアノテーション、条件付きテスト実行で説明されているいずれかのアノテーション、またはカスタムのExecutionConditionを介して無効化できます。

以下は、@Disabled テストクラスです。

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

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

    @Test
    void testWillBeSkipped() {
    }

}

以下は、@Disabled テストメソッドを含むテストクラスです。

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

class DisabledTestsDemo {

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

    @Test
    void testWillBeExecuted() {
    }

}

@Disabled は、理由を指定せずに宣言できます。ただし、JUnitチームは、テストクラスまたはテストメソッドが無効になっている理由について短い説明をすることを推奨しています。したがって、上記の例では両方とも、理由の使用を示しています(たとえば、@Disabled("バグ#42が解決されるまで無効"))。一部の開発チームでは、自動トレーサビリティのために、理由に課題追跡番号を含めることさえ要求しています。

@Disabled@Inherited ではありません。したがって、スーパークラスが @Disabled であるクラスを無効にする場合は、サブクラスで @Disabled を再宣言する必要があります。

2.8. 条件付きテスト実行

JUnit JupiterのExecutionCondition拡張APIを使用すると、開発者は特定の条件に基づいてコンテナまたはテストをプログラムで有効化または無効化できます。このような条件の最も単純な例は、DisabledCondition組み込みで、これは@Disabledアノテーションをサポートしています(テストの無効化を参照)。@Disabledに加えて、JUnit Jupiterは、org.junit.jupiter.api.conditionパッケージ内の他のいくつかのアノテーションベースの条件もサポートしており、開発者はコンテナとテストを宣言的に有効または無効にできます。複数のExecutionCondition拡張機能が登録されている場合、いずれかの条件がdisabledを返すとすぐに、コンテナまたはテストは無効になります。無効にする理由の詳細を提供したい場合は、これらの組み込み条件に関連付けられたすべてのアノテーションに、その目的で使用できるdisabledReason属性があります。

詳細については、ExecutionConditionと次のセクションを参照してください。

複合アノテーション

次のセクションにリストされている条件付きアノテーションは、カスタムの複合アノテーションを作成するためにメタアノテーションとして使用することもできます。@EnabledOnOsデモ@TestOnMacアノテーションは、@Test@EnabledOnOsを単一の再利用可能なアノテーションに組み合わせる方法を示しています。

JUnit Jupiterの条件付きアノテーションは@Inheritedではありません。したがって、サブクラスに同じセマンティクスを適用する場合は、各条件付きアノテーションを各サブクラスで再宣言する必要があります。

特に明記しない限り、次のセクションにリストされている各条件付きアノテーションは、特定のテストインターフェイス、テストクラス、またはテストメソッドで1回だけ宣言できます。条件付きアノテーションが、特定のエレメントに直接、間接的、またはメタ的に複数回存在する場合、JUnitによって最初に検出されたアノテーションのみが使用されます。追加の宣言はすべて無視されます。ただし、各条件付きアノテーションは、org.junit.jupiter.api.conditionパッケージの他の条件付きアノテーションと組み合わせて使用できることに注意してください。

2.8.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 {
}
アーキテクチャに基づく条件付き実行
@Test
@EnabledOnOs(architectures = "aarch64")
void onAarch64() {
    // ...
}

@Test
@DisabledOnOs(architectures = "x86_64")
void notOnX86_64() {
    // ...
}

@Test
@EnabledOnOs(value = MAC, architectures = "aarch64")
void onNewMacs() {
    // ...
}

@Test
@DisabledOnOs(value = MAC, architectures = "aarch64")
void notOnNewMacs() {
    // ...
}

2.8.2. Java ランタイム環境の条件

コンテナまたはテストは、@EnabledOnJreおよび@DisabledOnJreアノテーションを介して、特定のバージョンのJavaランタイム環境(JRE)で有効または無効にできます。または、@EnabledForJreRangeおよび@DisabledForJreRangeアノテーションを介して、特定の範囲のバージョンのJREで有効または無効にできます。範囲のデフォルトは、下限(min)としてJRE.JAVA_8、上限(max)としてJRE.OTHERであり、半開範囲の使用を可能にします。

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

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

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

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

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

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

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

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

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

2.8.3. ネイティブイメージの条件

コンテナまたはテストは、@EnabledInNativeImageおよび@DisabledInNativeImageアノテーションを介して、GraalVMネイティブイメージ内で有効または無効にできます。これらのアノテーションは通常、GraalVMのNative Build ToolsプロジェクトのGradleおよびMavenプラグインを使用してネイティブイメージ内でテストを実行する場合に使用されます。

@Test
@EnabledInNativeImage
void onlyWithinNativeImage() {
    // ...
}

@Test
@DisabledInNativeImage
void neverWithinNativeImage() {
    // ...
}

2.8.4. システムプロパティの条件

コンテナまたはテストは、@EnabledIfSystemPropertyおよび@DisabledIfSystemPropertyアノテーションを介して、named JVMシステムプロパティの値に基づいて有効または無効にできます。matches属性を介して提供された値は、正規表現として解釈されます。

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

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

JUnit Jupiter 5.6以降、@EnabledIfSystemPropertyおよび@DisabledIfSystemPropertyは、繰り返し可能なアノテーションです。したがって、これらのアノテーションは、テストインターフェイス、テストクラス、またはテストメソッドで複数回宣言できます。具体的には、これらのアノテーションは、特定のエレメントに直接、間接的、またはメタ的に存在する場合に検出されます。

2.8.5. 環境変数の条件

コンテナまたはテストは、@EnabledIfEnvironmentVariable および @DisabledIfEnvironmentVariable アノテーションを介して、基盤となるオペレーティングシステムの named 環境変数の値に基づいて有効または無効にできます。matches 属性を介して提供される値は、正規表現として解釈されます。

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

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

JUnit Jupiter 5.6 以降、@EnabledIfEnvironmentVariable および @DisabledIfEnvironmentVariable繰り返し可能なアノテーションです。したがって、これらのアノテーションは、テストインターフェース、テストクラス、またはテストメソッドで複数回宣言できます。具体的には、これらのアノテーションは、特定の要素に直接存在する場合、間接的に存在する場合、またはメタ的に存在する場合に見つかります。

2.8.6. カスタム条件

ExecutionCondition を実装する代わりに、コンテナまたはテストは、@EnabledIf および @DisabledIf アノテーションを介して構成された条件メソッドに基づいて有効または無効にできます。条件メソッドは、boolean 型の戻り値を持ち、引数なしまたは単一の ExtensionContext 引数のいずれかを受け入れることができます。

次のテストクラスは、@EnabledIf および @DisabledIf を介して customCondition というローカルメソッドを構成する方法を示しています。

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

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

boolean customCondition() {
    return true;
}

あるいは、条件メソッドはテストクラスの外部に配置することもできます。この場合、次の例に示すように、その完全修飾名で参照する必要があります。

package example;

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

class ExternalCustomConditionDemo {

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

}

class ExternalCondition {

    static boolean customCondition() {
        return true;
    }

}

条件メソッドを static にする必要があるケースがいくつかあります。

  • @EnabledIf または @DisabledIf がクラスレベルで使用されている場合

  • @EnabledIf または @DisabledIf@ParameterizedTest または @TestTemplate メソッドで使用されている場合

  • 条件メソッドが外部クラスにある場合

それ以外の場合は、static メソッドまたはインスタンスメソッドを条件メソッドとして使用できます。

ユーティリティクラスの既存の static メソッドをカスタム条件として使用できることはよくあります。

たとえば、java.awt.GraphicsEnvironment は、現在の環境がグラフィカルディスプレイをサポートしていないかどうかを判断するために使用できる public static boolean isHeadless() メソッドを提供します。したがって、グラフィカルサポートに依存するテストがある場合は、そのようなサポートが利用できない場合に無効にできます。例を以下に示します。

@DisabledIf(value = "java.awt.GraphicsEnvironment#isHeadless",
    disabledReason = "headless environment")

2.9. タグ付けとフィルタリング

テストクラスとメソッドは、@Tag アノテーションを介してタグ付けできます。これらのタグは、後で テストの検出と実行をフィルタリングするために使用できます。JUnit プラットフォームでのタグのサポートの詳細については、タグセクションを参照してください。

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

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

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

}
タグのカスタムアノテーションを作成する方法を示す例については、メタアノテーションと構成アノテーションを参照してください。

2.10. テスト実行順序

デフォルトでは、テストクラスとメソッドは、決定論的ではあるが意図的にわかりにくいアルゴリズムを使用して順序付けられます。これにより、テストスイートの後続の実行でテストクラスとテストメソッドが同じ順序で実行されることが保証され、反復可能なビルドが可能になります。

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

2.10.1. メソッドの順序

真のユニットテストは通常、実行される順序に依存すべきではありませんが、特定のテストメソッドの実行順序を強制する必要がある場合があります。たとえば、テストの順序が重要な統合テストまたは機能テストを作成する場合など、特に @TestInstance(Lifecycle.PER_CLASS) と組み合わせて使用する場合に必要です。

テストメソッドが実行される順序を制御するには、テストクラスまたはテストインターフェースに @TestMethodOrder をアノテーション付けし、目的の MethodOrderer 実装を指定します。独自のカスタム MethodOrderer を実装するか、次の組み込み MethodOrderer 実装のいずれかを使用できます。

次の例は、テストメソッドが @Order アノテーションを介して指定された順序で実行されることを保証する方法を示しています。

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

@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {

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

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

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

}
デフォルトのメソッド順序付けを設定する

junit.jupiter.testmethod.order.default 構成パラメーターを使用して、デフォルトで使用する MethodOrderer の完全修飾クラス名を指定できます。@TestMethodOrder アノテーションを介して構成された順序付けと同様に、提供されたクラスは MethodOrderer インターフェースを実装する必要があります。デフォルトの順序付けは、囲んでいるテストクラスまたはテストインターフェースに @TestMethodOrder アノテーションが存在しない限り、すべてのテストで使用されます。

たとえば、デフォルトで MethodOrderer.OrderAnnotation メソッド順序付けを使用するには、対応する完全修飾クラス名(例:src/test/resources/junit-platform.properties 内)に構成パラメーターを設定する必要があります。

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

同様に、MethodOrderer を実装するカスタムクラスの完全修飾名を指定できます。

2.10.2. クラスの順序

テストクラスは通常、実行される順序に依存すべきではありませんが、特定のテストクラスの実行順序を強制することが望ましい場合があります。テストクラス間に偶発的な依存関係がないことを確認するために、テストクラスをランダムな順序で実行する場合や、次のシナリオで概説されているように、ビルド時間を最適化するためにテストクラスを順序付けたい場合があります。

  • 以前に失敗したテストとより高速なテストを最初に実行する:「フェイルファースト」モード

  • 並列実行が有効になっている場合、より長いテストを最初にスケジュールする:「最短テストプラン実行時間」モード

  • その他のさまざまなユースケース

テストスイート全体のテストクラスの実行順序をグローバルに構成するには、junit.jupiter.testclass.order.default 構成パラメーターを使用して、使用する ClassOrderer の完全修飾クラス名を指定します。提供されたクラスは、ClassOrderer インターフェースを実装する必要があります。

独自のカスタム ClassOrderer を実装するか、次の組み込み ClassOrderer 実装のいずれかを使用できます。

たとえば、テストクラス@Order アノテーションが尊重されるようにするには、対応する完全修飾クラス名(例:src/test/resources/junit-platform.properties 内)で構成パラメーターを使用して ClassOrderer.OrderAnnotation クラスの順序付けを構成する必要があります。

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

構成された ClassOrderer は、すべてのトップレベルのテストクラス(static のネストされたテストクラスを含む)と @Nested テストクラスに適用されます。

トップレベルのテストクラスは互いに対して順序付けられます。一方、@Nested テストクラスは、同じ外側のクラスを共有する他の @Nested テストクラスに対して順序付けられます。

@Nested テストクラスのテストクラス実行順序をローカルに構成するには、順序付けたい @Nested テストクラスの外側のクラスに @TestClassOrder アノテーションを宣言し、@TestClassOrder アノテーション内で直接使用する ClassOrderer 実装へのクラス参照を提供します。構成された ClassOrderer は、@Nested テストクラスとそれらの @Nested テストクラスに再帰的に適用されます。ローカルの @TestClassOrder 宣言は、常に継承された @TestClassOrder 宣言または junit.jupiter.testclass.order.default 構成パラメーターを介してグローバルに構成された ClassOrderer をオーバーライドすることに注意してください。

次の例は、@Nested テストクラスが @Order アノテーションを介して指定された順序で実行されることを保証する方法を示しています。

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

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

    @Nested
    @Order(1)
    class PrimaryTests {

        @Test
        void test1() {
        }
    }

    @Nested
    @Order(2)
    class SecondaryTests {

        @Test
        void test2() {
        }
    }
}

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

個々のテストメソッドを分離して実行できるようにし、可変のテストインスタンスの状態による予期しない副作用を回避するために、JUnit は各テストメソッドを実行する前に、各テストクラスの新しいインスタンスを作成します(定義を参照)。この「メソッド単位」のテストインスタンスライフサイクルは、JUnit Jupiter のデフォルトの動作であり、以前のすべてのバージョンの JUnit に類似しています。

「メソッド単位」のテストインスタンスライフサイクルモードがアクティブな場合でも、特定のテストメソッド条件(例:@Disabled@DisabledOnOs など)を介して無効にされている場合、テストクラスはインスタンス化されることに注意してください。

JUnit Jupiter が全てのテストメソッドを同じテストインスタンス上で実行することを希望する場合は、テストクラスに @TestInstance(Lifecycle.PER_CLASS) アノテーションを付与してください。このモードを使用すると、新しいテストインスタンスがテストクラスごとに一度作成されます。そのため、テストメソッドがインスタンス変数に保存された状態に依存している場合は、@BeforeEach または @AfterEach メソッドでその状態をリセットする必要がある場合があります。

「クラスごと」モードには、デフォルトの「メソッドごと」モードよりもいくつかの追加の利点があります。具体的には、「クラスごと」モードでは、インターフェースの default メソッドだけでなく、非 static メソッドにも @BeforeAll および @AfterAll を宣言することが可能になります。したがって、「クラスごと」モードでは、@Nested テストクラスで @BeforeAll および @AfterAll メソッドを使用することも可能になります。

Java 16 以降では、@BeforeAll および @AfterAll メソッドを @Nested テストクラス内で static として宣言できます。

Kotlin プログラミング言語を使用してテストを作成している場合は、「クラスごと」のテストインスタンスライフサイクルモードに切り替えることで、非 static な @BeforeAll および @AfterAll ライフサイクルメソッドや @MethodSource ファクトリーメソッドをより簡単に実装できるかもしれません。

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

デフォルトのテストインスタンスライフサイクルモードを変更すると、一貫して適用されない場合、予測不可能な結果や脆弱なビルドにつながる可能性があります。たとえば、ビルド構成で「クラスごと」セマンティクスがデフォルトとして設定されているが、IDE でのテストは「メソッドごと」セマンティクスを使用して実行される場合、ビルドサーバーで発生するエラーをデバッグすることが難しくなる可能性があります。したがって、JVM システムプロパティ経由ではなく、JUnit Platform 構成ファイルでデフォルトを変更することをお勧めします。

2.12. ネストされたテスト

@Nested テストを使用すると、テスト作成者は複数のテストグループ間の関係をより表現できるようになります。このようなネストされたテストは、Java のネストされたクラスを利用し、テスト構造に関する階層的な思考を促進します。以下に、ソースコードと IDE 内での実行のスクリーンショットの両方で詳細な例を示します。

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

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

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

@DisplayName("A stack")
class TestingAStackDemo {

    Stack<Object> stack;

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

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

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

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

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

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

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

            String anElement = "an element";

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

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

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

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}

IDE でこの例を実行すると、GUI のテスト実行ツリーは次の画像のようになります。

writing tests nested test ide
IDE でのネストされたテストの実行

この例では、外側のテストの前提条件が、セットアップコードの階層的なライフサイクルメソッドを定義することにより、内側のテストで使用されます。たとえば、createNewStack()@BeforeEach ライフサイクルメソッドであり、それが定義されているテストクラス内と、そのクラスの下のネストツリーのすべてのレベルで使用されます。

外側のテストからのセットアップコードが内側のテストの実行前に実行されるという事実は、すべてのテストを独立して実行できるようにします。外側のテストのセットアップコードは常に実行されるため、外側のテストを実行せずに内側のテストのみを実行することもできます。

非 static ネストクラス(つまり、内部クラス)のみが @Nested テストクラスとして機能できます。ネストは任意に深くすることができ、それらの内部クラスは、1つの例外を除いて、完全なライフサイクルサポートの対象となります。 @BeforeAll および @AfterAll メソッドは、デフォルトでは機能しません。その理由は、Java が Java 16 より前の内部クラスで static メンバーを許可していないためです。ただし、この制限は、@Nested テストクラスに @TestInstance(Lifecycle.PER_CLASS) アノテーションを付与することで回避できます(テストインスタンスライフサイクルを参照)。Java 16 以降を使用している場合は、@BeforeAll および @AfterAll メソッドを @Nested テストクラス内で static として宣言でき、この制限は適用されなくなります。

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

以前のすべての JUnit バージョンでは、テストコンストラクタまたはメソッドはパラメータを持つことが許可されていませんでした(少なくとも標準の Runner 実装では)。JUnit Jupiter の主要な変更点の1つとして、テストコンストラクタとメソッドの両方がパラメータを持つことが許可されるようになりました。これにより、柔軟性が向上し、コンストラクタとメソッドの依存性注入が可能になります。

ParameterResolver は、実行時にパラメータを動的に解決したいテスト拡張機能の API を定義します。テストクラスのコンストラクタ、テストメソッド、またはライフサイクルメソッド定義を参照)がパラメータを受け入れる場合、そのパラメータは登録された ParameterResolver によって実行時に解決される必要があります。

現在、自動的に登録される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() {
    }

}
  • RepetitionExtension: @RepeatedTest@BeforeEach、または @AfterEach メソッドのメソッドパラメータが RepetitionInfo 型の場合、RepetitionExtensionRepetitionInfo のインスタンスを提供します。RepetitionInfo を使用すると、現在の繰り返し、繰り返しの合計回数、失敗した繰り返しの回数、および対応する @RepeatedTest の失敗しきい値に関する情報を取得できます。ただし、RepetitionExtension@RepeatedTest のコンテキスト外では登録されないことに注意してください。 繰り返しテストの例を参照してください。

  • TestReporterParameterResolver: コンストラクタまたはメソッドのパラメータが TestReporter 型の場合、TestReporterParameterResolverTestReporter のインスタンスを提供します。TestReporter を使用して、現在のテスト実行に関する追加データを公開できます。データは、TestExecutionListenerreportingEntryPublished() メソッドを介して利用でき、IDE で表示したり、レポートに含めたりできます。

    JUnit Jupiter では、JUnit 4 で stdout または stderr に情報を出力するために使用していた場所で TestReporter を使用する必要があります。@RunWith(JUnitPlatform.class) を使用すると、すべてのレポートされたエントリが stdout に出力されます。さらに、一部の IDE では、レポートエントリを 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) {
        Map<String, String> values = new HashMap<>();
        values.put("user name", "dk38");
        values.put("award year", "1974");

        testReporter.publishEntry(values);
    }

}
その他のパラメータリゾルバーは、@ExtendWith を介して適切な拡張機能を登録することによって明示的に有効にする必要があります。

カスタムの ParameterResolver の例については、RandomParametersExtension を参照してください。これは、本番環境での使用を意図したものではありませんが、拡張モデルとパラメータ解決プロセスの両方のシンプルさと表現力を示しています。MyRandomParametersTest は、@Test メソッドにランダムな値を注入する方法を示しています。

@ExtendWith(RandomParametersExtension.class)
class MyRandomParametersTest {

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

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

}

実際のユースケースについては、MockitoExtension および SpringExtension のソースコードを確認してください。

注入するパラメータの型が ParameterResolver の唯一の条件である場合は、汎用的な TypeBasedParameterResolver 基底クラスを使用できます。supportsParameters メソッドはバックグラウンドで実装され、パラメータ化された型をサポートします。

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

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 logger = Logger.getLogger(TestLifecycleLogger.class.getName());

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

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

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

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

}
interface TestInterfaceDynamicTestsDemo {

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

}

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

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

テストクラスでこれらのテストインターフェースを実装して、それらを適用できます。

class TestInterfaceDemo implements TestLifecycleLogger,
        TimeExecutionLogger, TestInterfaceDynamicTestsDemo {

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

}

TestInterfaceDemo を実行すると、次のような出力が得られます。

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

この機能のもう1つの考えられるアプリケーションは、インターフェースコントラクトのテストを作成することです。たとえば、Object.equals または Comparable.compareTo の実装がどのように動作するかを次のようにテストできます。

public interface Testable<T> {

    T createValue();

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

    T createNotEqualValue();

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

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

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

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

    T createSmallerValue();

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

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

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

}

テストクラスでは、両方のコントラクトインターフェースを実装することにより、対応するテストを継承できます。もちろん、抽象メソッドを実装する必要があります。

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

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

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

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

}
上記のテストは単なる例であり、完全ではありません。

2.15. 繰り返しテスト

JUnit Jupiter では、メソッドに @RepeatedTest アノテーションを付与し、必要な繰り返し回数を指定することで、テストを指定回数繰り返す機能を提供しています。繰り返される各テストの呼び出しは、通常の @Test メソッドの実行のように動作し、同じライフサイクルコールバックと拡張機能が完全にサポートされます。

以下の例は、repeatedTest() という名前のテストを自動的に10回繰り返す方法を示しています。

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

JUnit Jupiter 5.10 以降では、@RepeatedTest に失敗しきい値を設定できます。これは、残りの繰り返しを自動的にスキップする失敗回数を示します。指定された失敗回数が発生した後、残りの繰り返しの呼び出しをスキップするには、failureThreshold 属性を、繰り返しの総数より少ない正の数に設定します。

例えば、不安定であると思われるテストを繰り返し呼び出すために @RepeatedTest を使用している場合、テストが不安定であることを示すには1回の失敗で十分であり、残りの繰り返しを呼び出す必要はありません。その特定のユースケースをサポートするには、failureThreshold = 1 を設定します。ユースケースに応じて、しきい値を 1 より大きい数に設定することもできます。

デフォルトでは、failureThreshold 属性は Integer.MAX_VALUE に設定されており、失敗しきい値が適用されないことを示します。これは、繰り返しの失敗に関係なく、指定された繰り返し回数が呼び出されることを意味します。

@RepeatedTest メソッドの繰り返しが並列で実行される場合、失敗しきい値に関する保証はできません。したがって、並列実行が構成されている場合は、@RepeatedTest メソッドに @Execution(SAME_THREAD) アノテーションを付けることをお勧めします。詳細については、並列実行を参照してください。

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

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

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

  • {totalRepetitions}: 繰り返しの総数

特定の繰り返しのデフォルトの表示名は、次のパターンに基づいて生成されます:"repetition {currentRepetition} of {totalRepetitions}"。したがって、前の repeatedTest() 例の個々の繰り返しの表示名は、repetition 1 of 10repetition 2 of 10 などになります。@RepeatedTest メソッドの表示名を各繰り返しの名前に含める場合は、独自のカスタムパターンを定義するか、定義済みの RepeatedTest.LONG_DISPLAY_NAME パターンを使用できます。後者は "{displayName} :: repetition {currentRepetition} of {totalRepetitions}" と等しく、個々の繰り返しの表示名は repeatedTest() :: repetition 1 of 10repeatedTest() :: repetition 2 of 10 などになります。

現在の繰り返し、繰り返しの総数、失敗した繰り返しの数、および失敗しきい値に関する情報を取得するために、開発者は RepetitionInfo のインスタンスを @RepeatedTest@BeforeEach、または @AfterEach メソッドに注入することを選択できます。

2.15.1. 反復テストの例

このセクションの最後にある RepeatedTestsDemo クラスは、反復テストのいくつかの例を示しています。

repeatedTest() メソッドは前のセクションの例と同じです。一方、repeatedTestWithRepetitionInfo() は、現在の反復テストの繰り返し総数にアクセスするために RepetitionInfo のインスタンスをテストに注入する方法を示しています。

repeatedTestWithFailureThreshold() は、失敗しきい値を設定する方法を示し、2 回目の繰り返しごとに予期しない失敗をシミュレートします。結果の動作は、このセクションの最後にある ConsoleLauncher の出力で確認できます。

次の 2 つのメソッドは、各繰り返しの表示名に @RepeatedTest メソッドのカスタム @DisplayName を含める方法を示しています。customDisplayName() は、カスタム表示名をカスタムパターンと組み合わせ、次に TestInfo を使用して生成された表示名の形式を検証します。Repeat!@DisplayName 宣言から取得される {displayName} であり、1/1{currentRepetition}/{totalRepetitions} から取得されます。対照的に、customDisplayNameWithLongPattern() は前述の定義済みの RepeatedTest.LONG_DISPLAY_NAME パターンを使用します。

repeatedTestInGerman() は、反復テストの表示名を外国語(この場合はドイツ語)に翻訳する機能を示しており、Wiederholung 1 von 5Wiederholung 2 von 5 などの個々の繰り返しの名前になります。

beforeEach() メソッドには @BeforeEach アノテーションが付いているため、各反復テストの各繰り返しの前に実行されます。TestInfoRepetitionInfo をメソッドに注入することで、現在実行中の反復テストに関する情報を取得できることがわかります。INFO ログレベルを有効にして RepeatedTestsDemo を実行すると、次の出力が得られます。

INFO: About to execute repetition 1 of 10 for repeatedTest
INFO: About to execute repetition 2 of 10 for repeatedTest
INFO: About to execute repetition 3 of 10 for repeatedTest
INFO: About to execute repetition 4 of 10 for repeatedTest
INFO: About to execute repetition 5 of 10 for repeatedTest
INFO: About to execute repetition 6 of 10 for repeatedTest
INFO: About to execute repetition 7 of 10 for repeatedTest
INFO: About to execute repetition 8 of 10 for repeatedTest
INFO: About to execute repetition 9 of 10 for repeatedTest
INFO: About to execute repetition 10 of 10 for repeatedTest
INFO: About to execute repetition 1 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 2 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 3 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 4 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 5 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 1 of 8 for repeatedTestWithFailureThreshold
INFO: About to execute repetition 2 of 8 for repeatedTestWithFailureThreshold
INFO: About to execute repetition 3 of 8 for repeatedTestWithFailureThreshold
INFO: About to execute repetition 4 of 8 for repeatedTestWithFailureThreshold
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 static org.junit.jupiter.api.Assertions.fail;

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 = 8, failureThreshold = 2)
    void repeatedTestWithFailureThreshold(RepetitionInfo repetitionInfo) {
        // Simulate unexpected failure every second repetition
        if (repetitionInfo.getCurrentRepetition() % 2 == 0) {
            fail("Boom!");
        }
    }

    @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}")
    @DisplayName("Repeat!")
    void customDisplayName(TestInfo testInfo) {
        assertEquals("Repeat! 1/1", testInfo.getDisplayName());
    }

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

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

}

Unicode テーマを有効にした ConsoleLauncher を使用すると、RepeatedTestsDemo の実行結果は次のコンソール出力になります。

├─ RepeatedTestsDemo ✔
│  ├─ repeatedTest() ✔
│  │  ├─ repetition 1 of 10 ✔
│  │  ├─ repetition 2 of 10 ✔
│  │  ├─ repetition 3 of 10 ✔
│  │  ├─ repetition 4 of 10 ✔
│  │  ├─ repetition 5 of 10 ✔
│  │  ├─ repetition 6 of 10 ✔
│  │  ├─ repetition 7 of 10 ✔
│  │  ├─ repetition 8 of 10 ✔
│  │  ├─ repetition 9 of 10 ✔
│  │  └─ repetition 10 of 10 ✔
│  ├─ repeatedTestWithRepetitionInfo(RepetitionInfo) ✔
│  │  ├─ repetition 1 of 5 ✔
│  │  ├─ repetition 2 of 5 ✔
│  │  ├─ repetition 3 of 5 ✔
│  │  ├─ repetition 4 of 5 ✔
│  │  └─ repetition 5 of 5 ✔
│  ├─ repeatedTestWithFailureThreshold(RepetitionInfo) ✔
│  │  ├─ repetition 1 of 8 ✔
│  │  ├─ repetition 2 of 8 ✘ Boom!
│  │  ├─ repetition 3 of 8 ✔
│  │  ├─ repetition 4 of 8 ✘ Boom!
│  │  ├─ repetition 5 of 8 ↷ Failure threshold [2] exceeded
│  │  ├─ repetition 6 of 8 ↷ Failure threshold [2] exceeded
│  │  ├─ repetition 7 of 8 ↷ Failure threshold [2] exceeded
│  │  └─ repetition 8 of 8 ↷ Failure threshold [2] exceeded
│  ├─ 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 ✔

2.16. パラメータ化されたテスト

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

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

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

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

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

2.16.1. 必要なセットアップ

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

2.16.2. 引数の消費

パラメータ化されたテストメソッドは、通常、引数ソースインデックスとメソッドパラメータインデックス間の 1 対 1 の対応に従って、構成されたソースから直接引数を消費します(引数ソースを参照)。(@CsvSourceの例を参照)。ただし、パラメータ化されたテストメソッドでは、ソースからの引数を、メソッドに渡される単一のオブジェクトに集約することを選択することもできます(引数の集約を参照)。追加の引数は、ParameterResolver(たとえば、TestInfoTestReporter などのインスタンスを取得するため)によって提供される場合もあります。具体的には、パラメータ化されたテストメソッドは、次の規則に従って仮パラメータを宣言する必要があります。

  • 最初に 0 個以上のインデックス付き引数を宣言する必要があります。

  • 次に、0 個以上のアグリゲーターを宣言する必要があります。

  • 最後に、ParameterResolver によって提供される 0 個以上の引数を宣言する必要があります。

このコンテキストでは、インデックス付き引数とは、ArgumentsProvider によって提供される Arguments 内の特定のインデックスの引数であり、メソッドの仮パラメータリスト内の同じインデックスでパラメータ化されたメソッドへの引数として渡されます。アグリゲーターとは、ArgumentsAccessor 型の任意のパラメータ、または @AggregateWith アノテーションが付けられた任意のパラメータです。

AutoCloseable 引数

java.lang.AutoCloseable(または java.lang.AutoCloseable を拡張する java.io.Closeable)を実装する引数は、現在のパラメータ化されたテスト呼び出しで @AfterEach メソッドと AfterEachCallback 拡張機能が呼び出された後、自動的に閉じられます。

これを防ぐには、@ParameterizedTestautoCloseArguments 属性を false に設定します。具体的には、AutoCloseable を実装する引数が同じパラメータ化されたテストメソッドの複数の呼び出しで再利用される場合、引数が呼び出し間で閉じられないようにするために、メソッドに @ParameterizedTest(autoCloseArguments = false) のアノテーションを付ける必要があります。

2.16.3. 引数ソース

JUnit Jupiter は、すぐに使用できる多数のソースアノテーションを提供します。次の各サブセクションでは、それぞれの概要と例を簡単に説明します。追加情報については、org.junit.jupiter.params.provider パッケージの Javadoc を参照してください。

@ValueSource

@ValueSource は、最も単純なソースの 1 つです。リテラル値の単一の配列を指定でき、パラメータ化されたテストの呼び出しごとに単一の引数を提供する目的でのみ使用できます。

@ValueSource では、次の種類のリテラル値がサポートされています。

  • short

  • byte

  • int

  • long

  • float

  • double

  • char

  • boolean

  • java.lang.String

  • java.lang.Class

例えば、次の @ParameterizedTest メソッドは、それぞれ値 12、および 3 で 3 回呼び出されます。

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

コーナーケースを確認し、不正な入力が提供された場合のソフトウェアの適切な動作を検証するために、パラメータ化されたテストに null および の値を提供することが役立ちます。次のアノテーションは、単一の引数を受け入れるパラメータ化されたテストの null および空の値のソースとして機能します。

  • @NullSource: アノテーション付きの @ParameterizedTest メソッドに単一の null 引数を提供します。

    • @NullSource は、プリミティブ型のパラメータには使用できません。

  • @EmptySource: 次の型のパラメータについて、アノテーション付きの @ParameterizedTest メソッドに単一のの引数を提供します。java.lang.Stringjava.util.Collection(および public 引数なしコンストラクターを持つ具象サブタイプ)、java.util.Listjava.util.Setjava.util.SortedSetjava.util.NavigableSetjava.util.Map(および public 引数なしコンストラクターを持つ具象サブタイプ)、java.util.SortedMapjava.util.NavigableMap、プリミティブ配列(例:int[]char[][]など)、オブジェクト配列(例:String[]Integer[][]など)。

  • @NullAndEmptySource: @NullSource および @EmptySource の機能を組み合わせた複合アノテーションです。

パラメータ化されたテストに複数のさまざまな種類の 空白 文字列を提供する必要がある場合は、@ValueSource を使用してそれを実現できます(たとえば、@ValueSource(strings = {" ", "   ", "\t", "\n"}) など)。

また、@NullSource@EmptySource、および @ValueSource を組み合わせて、より広範囲の null、および空白入力をテストすることもできます。次の例は、文字列でこれを実現する方法を示しています。

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

構成された@NullAndEmptySourceアノテーションを利用することで、上記を以下のように簡略化できます。

@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = { " ", "   ", "\t", "\n" })
void nullEmptyAndBlankStrings(String text) {
    assertTrue(text == null || text.trim().isEmpty());
}
nullEmptyAndBlankStrings(String)パラメータ化テストメソッドの両方のバリアントは、6回の呼び出しになります。nullに対して1回、空の文字列に対して1回、@ValueSourceで指定された明示的な空白文字列に対して4回です。
@EnumSource

@EnumSourceは、Enum定数を便利に使用する方法を提供します。

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

アノテーションのvalue属性はオプションです。省略した場合、最初のメソッドパラメータの宣言された型が使用されます。Enum型を参照していない場合、テストは失敗します。したがって、メソッドパラメータはTemporalUnitとして宣言されているため、上記例ではvalue属性が必要です。これは、Enum型ではないChronoUnitによって実装されるインターフェースです。メソッドパラメータの型をChronoUnitに変更すると、以下のようにアノテーションから明示的なEnum型を省略できます。

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

アノテーションには、次の例のように、使用する定数を指定できるオプションのnames属性があります。省略した場合、すべての定数が使用されます。

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

@EnumSourceアノテーションには、テストメソッドに渡す定数を細かく制御できるオプションのmode属性もあります。たとえば、次の例のように、Enum定数プールから名前を除外したり、正規表現を指定したりできます。

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

@MethodSourceを使用すると、テストクラスまたは外部クラスの1つ以上のファクトリメソッドを参照できます。

テストクラス内のファクトリメソッドは、テストクラスに@TestInstance(Lifecycle.PER_CLASS)アノテーションが付いていない限りstaticである必要があります。一方、外部クラスのファクトリメソッドは常にstaticである必要があります。

各ファクトリメソッドは、引数ストリームを生成する必要があり、ストリーム内の各引数セットは、アノテーション付きの@ParameterizedTestメソッドの個々の呼び出しに対する物理的な引数として提供されます。一般的に言えば、これはArgumentsStream(つまり、Stream<Arguments>)に変換されます。ただし、実際の具体的な戻り値の型はさまざまな形式を取ることができます。このコンテキストでは、「ストリーム」とは、JUnitがStreamに確実に変換できるものであり、StreamDoubleStreamLongStreamIntStreamCollectionIteratorIterable、オブジェクトの配列、またはプリミティブの配列などです。ストリーム内の「引数」は、Argumentsのインスタンス、オブジェクトの配列(例:Object[])、またはパラメータ化されたテストメソッドが単一の引数を受け入れる場合は単一の値として提供できます。

単一のパラメータのみが必要な場合は、次の例に示すように、パラメータ型のインスタンスのStreamを返すことができます。

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

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

@MethodSourceを介してファクトリメソッド名を明示的に指定しない場合、JUnit Jupiterは慣例により、現在の@ParameterizedTestメソッドと同じ名前を持つファクトリメソッドを検索します。これは次の例で示されています。

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

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

プリミティブ型(DoubleStreamIntStream、および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インターフェースで定義された静的なファクトリメソッドであることに注意してください。また、Arguments.of(Object…​)arguments(Object…​)の代替として使用できます。

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

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

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

package example;

import java.util.stream.Stream;

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

class ExternalMethodSourceDemo {

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

class StringsProviders {

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

ファクトリメソッドは、ParameterResolver拡張APIの登録済み実装によって提供されるパラメータを宣言できます。次の例では、テストクラスにそのようなメソッドが1つしかないため、ファクトリメソッドはその名前で参照されます。同じ名前のローカルメソッドが複数ある場合は、それらを区別するためにパラメータを提供することもできます。たとえば、@MethodSource("factoryMethod()")または@MethodSource("factoryMethod(java.lang.String)")です。あるいは、ファクトリメソッドは、完全修飾メソッド名(例:@MethodSource("example.MyTests#factoryMethod(java.lang.String)"))で参照できます。

@RegisterExtension
static final IntegerResolver integerResolver = new IntegerResolver();

@ParameterizedTest
@MethodSource("factoryMethodWithArguments")
void testWithFactoryMethodWithArguments(String argument) {
    assertTrue(argument.startsWith("2"));
}

static Stream<Arguments> factoryMethodWithArguments(int quantity) {
    return Stream.of(
            arguments(quantity + " apples"),
            arguments(quantity + " lemons")
    );
}

static class IntegerResolver implements ParameterResolver {

    @Override
    public boolean supportsParameter(ParameterContext parameterContext,
            ExtensionContext extensionContext) {

        return parameterContext.getParameter().getType() == int.class;
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext,
            ExtensionContext extensionContext) {

        return 2;
    }

}
@CsvSource

@CsvSourceを使用すると、引数リストをカンマ区切り値(つまり、CSVStringリテラル)として表現できます。@CsvSourcevalue属性を介して提供される各文字列は、CSVレコードを表し、パラメータ化されたテストの1回の呼び出しになります。最初のレコードは、必要に応じてCSVヘッダーを提供するために使用できます(詳細と例については、useHeadersInDisplayName属性のJavadocを参照してください)。

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

デフォルトの区切り文字はカンマ(,)ですが、delimiter属性を設定することで別の文字を使用できます。あるいは、delimiterString属性を使用すると、単一の文字の代わりにString区切り文字を使用できます。ただし、両方の区切り文字属性を同時に設定することはできません。

デフォルトでは、@CsvSourceは引用符として単一引用符(')を使用しますが、これはquoteCharacter属性で変更できます。上記の例と以下の表にある'lemon, lime'の値を確認してください。空の引用符付きの値('')は、emptyValue属性が設定されていない限り、空のStringになります。一方、完全にの値はnull参照として解釈されます。1つ以上のnullValuesを指定することにより、カスタム値をnull参照として解釈できます(以下の表のNILの例を参照)。null参照のターゲット型がプリミティブ型の場合、ArgumentConversionExceptionがスローされます。

引用符で囲まれていない空の値は、nullValues属性で構成されたカスタム値に関係なく、常にnull参照に変換されます。

引用符付きの文字列内を除き、CSV列の先頭と末尾の空白はデフォルトでトリミングされます。この動作は、ignoreLeadingAndTrailingWhitespace属性をtrueに設定することで変更できます。

入力例 結果の引数リスト

@CsvSource({ "apple, banana" })

"apple", "banana"

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

"apple", "lemon, lime"

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

"apple", ""

@CsvSource({ "apple, " })

"apple", null

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

"apple", "banana", null

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

" apple ", " banana"

使用しているプログラミング言語がテキストブロック(たとえば、Java SE 15以降)をサポートしている場合は、@CsvSourcetextBlock属性を代わりに使用できます。テキストブロック内の各レコードはCSVレコードを表し、パラメータ化されたテストの1回の呼び出しになります。最初のレコードは、以下の例のように、useHeadersInDisplayName属性をtrueに設定することで、必要に応じてCSVヘッダーを提供するために使用できます。

テキストブロックを使用すると、前の例は次のように実装できます。

@ParameterizedTest(name = "[{index}] {arguments}")
@CsvSource(useHeadersInDisplayName = true, textBlock = """
    FRUIT,         RANK
    apple,         1
    banana,        2
    'lemon, lime', 0xF1
    strawberry,    700_000
    """)
void testWithCsvSource(String fruit, int rank) {
    // ...
}

前の例で生成された表示名には、CSVヘッダー名が含まれています。

[1] FRUIT = apple, RANK = 1
[2] FRUIT = banana, RANK = 2
[3] FRUIT = lemon, lime, RANK = 0xF1
[4] FRUIT = strawberry, RANK = 700_000

value属性を介して提供されるCSVレコードとは対照的に、テキストブロックにはコメントを含めることができます。#記号で始まる行はコメントとして扱われ、無視されます。ただし、#記号は、先頭の空白なしで、行の最初の文字である必要があります。したがって、テキストブロックの終了区切り文字(""")は、入力の最後の行の末尾か、以下の例に示すように、入力の残りの部分と左揃えにして次の行に配置することをお勧めします。

@ParameterizedTest
@CsvSource(delimiter = '|', quoteCharacter = '"', textBlock = """
    #-----------------------------
    #    FRUIT     |     RANK
    #-----------------------------
         apple     |      1
    #-----------------------------
         banana    |      2
    #-----------------------------
      "lemon lime" |     0xF1
    #-----------------------------
       strawberry  |    700_000
    #-----------------------------
    """)
void testWithCsvSource(String fruit, int rank) {
    // ...
}

Javaのテキストブロック機能は、コードがコンパイルされるときに付随的な空白を自動的に削除します。ただし、GroovyやKotlinなどの他のJVM言語はそうではありません。したがって、Java以外のプログラミング言語を使用しており、テキストブロックに引用符で囲まれた文字列内にコメントや改行が含まれている場合は、テキストブロック内に先頭の空白がないことを確認する必要があります。

@CsvFileSource

@CsvFileSourceを使用すると、クラスパスまたはローカルファイルシステムのカンマ区切り値(CSV)ファイルを使用できます。CSVファイルの各レコードは、パラメータ化されたテストの1回の呼び出しになります。最初のレコードは、必要に応じてCSVヘッダーを提供するために使用できます。numLinesToSkip属性を介してヘッダーを無視するようにJUnitに指示できます。ヘッダーを表示名で使用する場合は、useHeadersInDisplayName属性をtrueに設定できます。以下の例では、numLinesToSkipuseHeadersInDisplayNameの使用法を示しています。

デフォルトの区切り文字はカンマ(,)ですが、delimiter属性を設定することで別の文字を使用できます。あるいは、delimiterString属性を使用すると、単一の文字の代わりにString区切り文字を使用できます。ただし、両方の区切り文字属性を同時に設定することはできません。

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

@ParameterizedTest
@CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSourceFromFile(String country, int reference) {
    assertNotNull(country);
    assertNotEquals(0, reference);
}

@ParameterizedTest(name = "[{index}] {arguments}")
@CsvFileSource(resources = "/two-column.csv", useHeadersInDisplayName = true)
void testWithCsvFileSourceAndHeaders(String country, int reference) {
    assertNotNull(country);
    assertNotEquals(0, reference);
}
two-column.csv
COUNTRY, REFERENCE
Sweden, 1
Poland, 2
"United States of America", 3
France, 700_000

次のリストは、上記の最初の2つのパラメータ化されたテストメソッドで生成された表示名を示しています。

[1] country=Sweden, reference=1
[2] country=Poland, reference=2
[3] country=United States of America, reference=3
[4] country=France, reference=700_000

次のリストは、CSVヘッダー名を使用する上記の最後のパラメータ化されたテストメソッドで生成された表示名を示しています。

[1] COUNTRY = Sweden, REFERENCE = 1
[2] COUNTRY = Poland, REFERENCE = 2
[3] COUNTRY = United States of America, REFERENCE = 3
[4] COUNTRY = France, REFERENCE = 700_000

@CsvSourceで使用されるデフォルトの構文とは対照的に、@CsvFileSourceはデフォルトで引用符として二重引用符(")を使用しますが、これはquoteCharacter属性で変更できます。上記の例にある"United States of America"の値を確認してください。空の引用符付きの値("")は、emptyValue属性が設定されていない限り、空のStringになります。一方、完全にの値はnull参照として解釈されます。1つ以上のnullValuesを指定することにより、カスタム値をnull参照として解釈できます。null参照のターゲット型がプリミティブ型の場合、ArgumentConversionExceptionがスローされます。

引用符で囲まれていない空の値は、nullValues属性で構成されたカスタム値に関係なく、常にnull参照に変換されます。

引用符付きの文字列内を除き、CSV列の先頭と末尾の空白はデフォルトでトリミングされます。この動作は、ignoreLeadingAndTrailingWhitespace属性をtrueに設定することで変更できます。

@ArgumentsSource

@ArgumentsSourceは、カスタムの再利用可能なArgumentsProviderを指定するために使用できます。ArgumentsProviderの実装は、トップレベルクラスまたはstaticネストクラスとして宣言する必要があることに注意してください。

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

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

アノテーションも消費するカスタムArgumentsProviderを実装する場合(ValueArgumentsProviderCsvArgumentsProviderなどの組み込みプロバイダーのように)、AnnotationBasedArgumentsProviderクラスを拡張できます。

2.16.4. 引数変換

拡大変換

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

暗黙的変換

@CsvSourceのようなユースケースをサポートするために、JUnit Jupiterは多くの組み込みの暗黙的な型コンバーターを提供します。変換プロセスは、各メソッドパラメータの宣言された型によって異なります。

たとえば、@ParameterizedTestTimeUnit型のパラメータを宣言し、宣言されたソースによって提供される実際の型がStringである場合、文字列は対応するTimeUnit Enum定数に自動的に変換されます。

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

Stringインスタンスは、次のターゲット型に暗黙的に変換されます。

10進数、16進数、および8進数のStringリテラルは、それらの整数型(byteshortintlong、およびそれらのボックス化された対応物)に変換されます。
ターゲット型

boolean/Boolean

"true"true ('true' または 'false' の値を大文字・小文字を区別せずに受け付けます)

byte/Byte

"15""0xF"、または "017"(byte) 15

char/Character

"o"'o'

short/Short

"15""0xF"、または "017"(short) 15

int/Integer

"15""0xF"、または "017"15

long/Long

"15""0xF"、または "017"15L

float/Float

"1.0"1.0f

double/Double

"1.0"1.0d

Enum サブクラス

"SECONDS"TimeUnit.SECONDS

java.io.File

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

java.lang.Class

"java.lang.Integer"java.lang.Integer.class (ネストされたクラスには $ を使用します。例: "java.lang.Thread$State"

java.lang.Class

"byte"byte.class (プリミティブ型がサポートされています)

java.lang.Class

"char[]"char[].class (配列型がサポートされています)

java.math.BigDecimal

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

java.math.BigInteger

"1234567890123456789"new BigInteger("1234567890123456789")

java.net.URI

"https://junit.dokyumento.jp/"URI.create("https://junit.dokyumento.jp/")

java.net.URL

"https://junit.dokyumento.jp/"URI.create("https://junit.dokyumento.jp/").toURL()

java.nio.charset.Charset

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

java.nio.file.Path

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

java.time.Duration

"PT3S"Duration.ofSeconds(3)

java.time.Instant

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

java.time.LocalDateTime

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

java.time.LocalDate

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

java.time.LocalTime

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

java.time.MonthDay

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

java.time.OffsetDateTime

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

java.time.OffsetTime

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

java.time.Period

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

java.time.YearMonth

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

java.time.Year

"2017"Year.of(2017)

java.time.ZonedDateTime

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

java.time.ZoneId

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

java.time.ZoneOffset

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

java.util.Currency

"JPY"Currency.getInstance("JPY")

java.util.Locale

"en"new Locale("en")

java.util.UUID

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

フォールバック String-to-Object 変換

上記表にリストされているターゲット型への文字列からの暗黙的な変換に加えて、JUnit Jupiterは、ターゲット型が下記で定義されている適切なファクトリメソッドまたはファクトリコンストラクタを正確に1つ宣言している場合に、Stringから特定のターゲット型への自動変換のためのフォールバックメカニズムも提供します。

  • ファクトリメソッド:ターゲット型で宣言された非プライベートでstaticなメソッドで、単一のString引数を受け取り、ターゲット型のインスタンスを返します。メソッドの名前は任意で、特定の規則に従う必要はありません。

  • ファクトリコンストラクタ:ターゲット型で、単一のString引数を受け入れる非プライベートコンストラクタ。ターゲット型は、トップレベルクラスまたはstaticネストクラスとして宣言する必要があります。

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

たとえば、次の@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を明示的に指定できます。ArgumentConverterの実装は、トップレベルクラスまたはstaticネストクラスとして宣言する必要があることに注意してください。

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

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

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

コンバータが1つの型から別の型への変換のみを目的とする場合は、ボイラープレートの型チェックを回避するためにTypedArgumentConverterを拡張できます。

public class ToLengthArgumentConverter extends TypedArgumentConverter<String, Integer> {

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

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

}

明示的な引数コンバータは、テストおよび拡張機能の作成者が実装することを目的としています。したがって、junit-jupiter-paramsは、参照実装としても機能する可能性のある単一の明示的な引数コンバータ(JavaTimeArgumentConverter)のみを提供します。これは、構成されたアノテーションJavaTimeConversionPatternを介して使用されます。

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

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

カスタムのArgumentConverterを実装してアノテーション(JavaTimeArgumentConverterなど)も消費する場合は、AnnotationBasedArgumentConverterクラスを拡張できます。

2.16.5. 引数の集約

デフォルトでは、@ParameterizedTestメソッドに提供される各引数は、単一のメソッドパラメータに対応します。したがって、多数の引数を提供する予定の引数ソースは、大きなメソッドシグネチャにつながる可能性があります。

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

さらに、ArgumentsAccessor.getInvocationIndex()を使用して現在のテスト呼び出しインデックスを取得できます。

@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は、カスタムの再利用可能なアグリゲータの使用もサポートしています。

カスタムアグリゲータを使用するには、ArgumentsAggregatorインターフェースを実装し、@ParameterizedTestメソッドの互換性のあるパラメータで@AggregateWithアノテーションを介して登録します。集約の結果は、パラメータ化されたテストが呼び出されるときに、対応するパラメータの引数として提供されます。ArgumentsAggregatorの実装は、トップレベルクラスまたはstaticネストクラスとして宣言する必要があることに注意してください。

@ParameterizedTest
@CsvSource({
    "Jane, Doe, F, 1990-05-20",
    "John, Doe, M, 1990-10-22"
})
void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) {
    // perform assertions against person
}
public class PersonAggregator implements ArgumentsAggregator {
    @Override
    public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) {
        return new Person(arguments.getString(0),
                          arguments.getString(1),
                          arguments.get(2, Gender.class),
                          arguments.get(3, LocalDate.class));
    }
}

コードベース全体の複数のパラメータ化されたテストメソッドに対して、@AggregateWith(MyTypeAggregator.class)を繰り返し宣言している場合は、@AggregateWith(MyTypeAggregator.class)でメタアノテーションが付けられた@CsvToMyTypeなどのカスタムの構成されたアノテーションを作成するとよいでしょう。次の例は、カスタム@CsvToPersonアノテーションを使用した実際の動作を示しています。

@ParameterizedTest
@CsvSource({
    "Jane, Doe, F, 1990-05-20",
    "John, Doe, M, 1990-10-22"
})
void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) {
    // perform assertions against person
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AggregateWith(PersonAggregator.class)
public @interface CsvToPerson {
}

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

デフォルトでは、パラメータ化されたテスト呼び出しの表示名には、呼び出しインデックスと、その特定の呼び出しのすべての引数のString表現が含まれます。それらのそれぞれには、バイトコードに存在する場合(Javaの場合、テストコードは-parametersコンパイラフラグでコンパイルする必要があります)、パラメータ名が先行します(引数がArgumentsAccessorまたはArgumentAggregatorを介してのみ利用可能な場合を除く)。

ただし、次の例のように、@ParameterizedTestアノテーションのname属性を使用して、呼び出しの表示名をカスタマイズできます。

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

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

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

nameMessageFormatパターンであることに注意してください。したがって、表示するには、単一引用符(')を二重の単一引用符('')として表す必要があります。

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

プレースホルダー 説明

{displayName}

メソッドの表示名

{index}

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

{arguments}

完全なコンマ区切りの引数リスト

{argumentsWithNames}

パラメータ名付きの完全なコンマ区切り引数リスト

{0}{1}、…​

個々の引数

表示名に引数を含める場合、構成された最大長を超える場合は、引数の文字列表現が切り捨てられます。この制限は、junit.jupiter.params.displayname.argument.maxlength構成パラメータを介して構成でき、デフォルトは512文字です。

@MethodSourceまたは@ArgumentsSourceを使用する場合は、Named APIを使用して引数のカスタム名を指定できます。カスタム名は、以下の例のように、引数が呼び出しの表示名に含まれている場合に使用されます。

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

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

プロジェクト内のすべてのパラメータ化されたテストにデフォルトの名前パターンを設定する場合は、次の例に示すように、junit-platform.propertiesファイルでjunit.jupiter.params.displayname.default構成パラメータを宣言できます(他のオプションについては、構成パラメータを参照してください)。

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

パラメータ化されたテストの表示名は、次の優先順位ルールに従って決定されます

  1. 存在する場合は、@ParameterizedTestname属性

  2. 存在する場合は、junit.jupiter.params.displayname.default構成パラメータの値

  3. @ParameterizedTestで定義されたDEFAULT_DISPLAY_NAME定数

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

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

@ParameterizedTestメソッドでParameterResolver拡張機能を使用できます。ただし、引数ソースによって解決されるメソッドパラメータは、引数リストの先頭に来る必要があります。テストクラスには、異なるパラメータリストを持つ通常のテストとパラメータ化されたテストが含まれる可能性があるため、引数ソースからの値はライフサイクルメソッド(@BeforeEachなど)とテストクラスコンストラクタに対しては解決されません。

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

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

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

2.17. テストテンプレート

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

繰り返しテストパラメータ化されたテストは、テストテンプレートの組み込みの特殊化です。

2.18. 動的テスト

アノテーションで説明されている JUnit Jupiter の標準的な @Test アノテーションは、JUnit 4 の @Test アノテーションと非常によく似ています。どちらもテストケースを実装するメソッドを表します。これらのテストケースは、コンパイル時に完全に指定され、ランタイム中に発生するいかなる事象によっても動作を変更できないという意味で静的です。アサーションは、動的な動作の基本的な形式を提供しますが、その表現力は意図的にかなり制限されています。

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

@Test メソッドとは対照的に、@TestFactory メソッドはそれ自体がテストケースではなく、テストケースのファクトリです。したがって、動的テストはファクトリの成果物です。技術的に言えば、@TestFactory メソッドは、単一の DynamicNode または DynamicNode インスタンスの StreamCollectionIterableIterator、または配列を返す必要があります。DynamicNode のインスタンス化可能なサブクラスは、DynamicContainerDynamicTest です。DynamicContainer インスタンスは、表示名と動的な子ノードのリストで構成されており、動的ノードの任意のネストされた階層の作成を可能にします。DynamicTest インスタンスは遅延実行され、テストケースの動的かつ非決定論的な生成を可能にします。

@TestFactory によって返される Stream は、stream.close() を呼び出すことによって適切に閉じられ、Files.lines() などのリソースを安全に使用できます。

@Test メソッドと同様に、@TestFactory メソッドは private または static にしてはならず、オプションで ParameterResolver によって解決されるパラメータを宣言できます。

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

動的テストのライフサイクル
動的テストの実行ライフサイクルは、標準的な @Test ケースの場合とは大きく異なります。具体的には、個々の動的テストに対するライフサイクルコールバックはありません。これは、@BeforeEach および @AfterEach メソッドと、それに対応する拡張コールバックが、@TestFactory メソッドに対しては実行されますが、各動的テストに対しては実行されないことを意味します。言い換えれば、動的テストのラムダ式内でテストインスタンスからフィールドにアクセスする場合、それらのフィールドは、同じ @TestFactory メソッドによって生成された個々の動的テストの実行間隔で、コールバックメソッドまたは拡張機能によってリセットされません。

JUnit Jupiter 5.10.2 の時点では、動的テストは常にファクトリメソッドによって作成される必要があります。ただし、これは将来のリリースで登録機能によって補完される可能性があります。

2.18.1. 動的テストの例

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

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

次の 6 つのメソッドは、DynamicTest インスタンスの CollectionIterableIterator、配列、または Stream の生成を示しています。これらの例のほとんどは、実際には動的な動作を示していませんが、原則としてサポートされている戻り型を示しているにすぎません。ただし、dynamicTestsFromStream()dynamicTestsFromIntStream() は、与えられた文字列のセットまたは入力数値の範囲に対して動的テストを生成する方法を示しています。

次のメソッドは、本質的に真に動的です。generateRandomNumberOfTests() は、乱数、表示名ジェネレーター、テストエグゼキューターを生成する Iterator を実装し、それらすべてを DynamicTest.stream() に提供します。generateRandomNumberOfTests() の非決定論的な動作は、もちろんテストの再現性と矛盾するため、注意して使用する必要がありますが、動的テストの表現力と能力を示すのに役立ちます。

次のメソッドは、柔軟性の点で generateRandomNumberOfTests() と似ています。ただし、dynamicTestsFromStreamFactoryMethod() は、DynamicTest.stream() ファクトリメソッドを介して、既存の Stream から動的テストのストリームを生成します。

デモンストレーションのために、dynamicNodeSingleTest() メソッドはストリームではなく単一の DynamicTest を生成し、dynamicNodeSingleContainer() メソッドは DynamicContainer を利用して動的テストのネストされた階層を生成します。

import static example.util.StringUtils.isPalindrome;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import static org.junit.jupiter.api.Named.named;

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

import example.util.Calculator;

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

class DynamicTestsDemo {

    private final Calculator calculator = new Calculator();

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

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

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

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

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

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

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

    @TestFactory
    Stream<DynamicTest> generateRandomNumberOfTestsFromIterator() {

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

            Random random = new Random();
            int current;

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

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

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

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

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

    @TestFactory
    Stream<DynamicTest> dynamicTestsFromStreamFactoryMethod() {
        // Stream of palindromes to check
        Stream<String> inputStream = Stream.of("racecar", "radar", "mom", "dad");

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

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

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

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

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

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

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

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

}

2.18.2. 動的テストの URI テストソース

JUnit Platform は、IDE およびビルドツールでその場所へ移動するために使用される、テストまたはコンテナのソースの表現である TestSource を提供します。

動的テストまたは動的コンテナの TestSource は、それぞれ DynamicTest.dynamicTest(String, URI, Executable) または DynamicContainer.dynamicContainer(String, URI, Stream) ファクトリメソッドを介して提供できる java.net.URI から構築できます。URI は、次の TestSource 実装のいずれかに変換されます。

ClasspathResourceSource

URIclasspath スキームが含まれている場合 — たとえば、classpath:/test/foo.xml?line=20,column=2

DirectorySource

URI がファイルシステムに存在するディレクトリを表す場合。

FileSource

URI がファイルシステムに存在するファイルを表す場合。

MethodSource

URImethod スキームと完全修飾メソッド名 (FQMN) が含まれている場合 — たとえば、method:org.junit.Foo#bar(java.lang.String, java.lang.String[])。FQMN のサポートされている形式については、DiscoverySelectors.selectMethod(String) の Javadoc を参照してください。

ClassSource

URIclass スキームと完全修飾クラス名が含まれている場合 — たとえば、class:org.junit.Foo?line=42

UriSource

上記のいずれの TestSource 実装も適用できない場合。

2.19. タイムアウト

@Timeout アノテーションを使用すると、テスト、テストファクトリ、テストテンプレート、またはライフサイクルメソッドの実行時間が指定された期間を超えた場合に失敗するように宣言できます。期間の時間単位は、デフォルトでは秒ですが、構成可能です。

次の例は、ライフサイクルメソッドとテストメソッドに @Timeout が適用される方法を示しています。

class TimeoutDemo {

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

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

    @Test
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = ThreadMode.SEPARATE_THREAD)
    void failsIfExecutionTimeExceeds500MillisecondsInSeparateThread() {
        // fails if execution time exceeds 500 milliseconds, the test code is executed in a separate thread
    }

}

テストクラスとそのすべての @Nested クラス内のすべてのテストメソッドに同じタイムアウトを適用するには、クラスレベルで @Timeout アノテーションを宣言できます。これにより、そのクラスとその @Nested クラス内のすべてのテスト、テストファクトリ、およびテストテンプレートメソッドに適用されます。ただし、特定のメソッドまたは @Nested クラスの @Timeout アノテーションによってオーバーライドされない場合です。クラスレベルで宣言された @Timeout アノテーションはライフサイクルメソッドには適用されないことに注意してください。

@TestFactory メソッドに @Timeout を宣言すると、ファクトリメソッドが指定された期間内に戻ることをチェックしますが、ファクトリによって生成された個々の DynamicTest の実行時間は検証しません。その目的には、assertTimeout() または assertTimeoutPreemptively() を使用してください。

@TestTemplate メソッド (たとえば、@RepeatedTest または @ParameterizedTest) に @Timeout が存在する場合、各呼び出しには指定されたタイムアウトが適用されます。

2.19.1. スレッドモード

タイムアウトは、次の 3 つのスレッドモードのいずれかを使用して適用できます: SAME_THREADSEPARATE_THREAD、または INFERRED

SAME_THREAD が使用されている場合、アノテーション付きメソッドの実行はテストのメインスレッドで続行されます。タイムアウトを超過すると、メインスレッドは別のスレッドから割り込まれます。これは、現在実行中のスレッドに依存するメカニズムを使用する Spring などのフレームワークとの相互運用性を確保するために行われます — たとえば、ThreadLocal トランザクション管理など。

反対に、SEPARATE_THREAD が使用されている場合、assertTimeoutPreemptively() アサーションと同様に、アノテーション付きメソッドの実行は別のスレッドで続行されます。これにより、望ましくない副作用が発生する可能性があります。 assertTimeoutPreemptively() を使用したプリエンプティブタイムアウト を参照してください。

INFERRED (デフォルト) スレッドモードが使用されている場合、スレッドモードは junit.jupiter.execution.timeout.thread.mode.default 構成パラメータを介して解決されます。提供された構成パラメータが無効であるか、存在しない場合、SAME_THREAD がフォールバックとして使用されます。

2.19.2. デフォルトのタイムアウト

次の 構成パラメータ を使用すると、特定のカテゴリのすべてのメソッドに対して、それらまたは囲んでいるテストクラスが @Timeout でアノテーションされていない限り、デフォルトのタイムアウトを指定できます

junit.jupiter.execution.timeout.default

すべてのテスト可能メソッドおよびライフサイクルメソッドのデフォルトタイムアウト

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

すべてのテスト可能メソッドのデフォルトタイムアウト

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

@Test メソッドのデフォルトタイムアウト

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

@TestTemplate メソッドのデフォルトタイムアウト

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

@TestFactory メソッドのデフォルトタイムアウト

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

すべてのライフサイクルメソッドのデフォルトタイムアウト

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

@BeforeAll メソッドのデフォルトタイムアウト

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

@BeforeEach メソッドのデフォルトタイムアウト

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

@AfterEach メソッドのデフォルトタイムアウト

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

@AfterAll メソッドのデフォルトタイムアウト

より具体的な構成パラメータは、より具体性の低いものをオーバーライドします。たとえば、junit.jupiter.execution.timeout.test.method.default は、junit.jupiter.execution.timeout.testable.method.default をオーバーライドし、junit.jupiter.execution.timeout.default をオーバーライドします。

このような構成パラメータの値は、大文字と小文字を区別しない次の形式である必要があります: <number> [ns|μs|ms|s|m|h|d]。数値と単位の間のスペースは省略できます。単位を指定しない場合は、秒を使用することと同じです。

表 1. タイムアウト構成パラメータ値の例
パラメータ値 同等のアノテーション

42

@Timeout(42)

42 ns

@Timeout(value = 42, unit = NANOSECONDS)

42 μs

@Timeout(value = 42, unit = MICROSECONDS)

42 ms

@Timeout(value = 42, unit = MILLISECONDS)

42 s

@Timeout(value = 42, unit = SECONDS)

42 m

@Timeout(value = 42, unit = MINUTES)

42 h

@Timeout(value = 42, unit = HOURS)

42 d

@Timeout(value = 42, unit = DAYS)

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

非同期コードを扱う場合、アサーションを実行する前に何かが起こるのを待つ間、ポーリングを行うテストを書くのが一般的です。場合によっては、CountDownLatch や他の同期メカニズムを使用するようにロジックを書き換えることができますが、それが不可能な場合もあります。たとえば、テスト対象が外部メッセージブローカーのチャネルにメッセージを送信する場合、チャネルを介してメッセージが正常に送信されるまでアサーションを実行できない場合などです。このような非同期テストでは、非同期メッセージが正常に配信されない場合のように、テストスイートが永久にハングアップしないように、タイムアウトを設定する必要があります。

ポーリングを行う非同期テストにタイムアウトを設定することで、テストが永久に実行されないようにすることができます。次の例は、JUnit Jupiter の @Timeout アノテーションを使用してこれを実現する方法を示しています。このテクニックを使用すると、「ポーリングして待機」ロジックを非常に簡単に実装できます。

@Test
@Timeout(5) // Poll at most 5 seconds
void pollUntil() throws InterruptedException {
    while (asynchronousResultNotAvailable()) {
        Thread.sleep(250); // custom poll interval
    }
    // Obtain the asynchronous result and perform assertions
}
ポーリング間隔をより詳細に制御したり、非同期テストの柔軟性を高めたりする必要がある場合は、Awaitility などの専用ライブラリの使用を検討してください。

2.19.4. @Timeout をグローバルに無効にする

デバッグセッションでコードをステップ実行する場合、固定タイムアウト制限がテストの結果に影響を与える可能性があります。例えば、すべてのアサーションが満たされているにもかかわらず、テストが失敗とマークされるなどです。

JUnit Jupiter は、タイムアウトを適用するタイミングを設定するために、junit.jupiter.execution.timeout.mode 設定パラメータをサポートしています。3 つのモードがあります: enableddisabled、および disabled_on_debug。デフォルトモードは enabled です。VM ランタイムは、その入力パラメータのいずれかが -agentlib:jdwp または -Xrunjdwp で始まる場合にデバッグモードで実行されているとみなされます。このヒューリスティックは、disabled_on_debug モードによって照会されます。

2.20. 並列実行

デフォルトでは、JUnit Jupiter のテストは単一のスレッドで順番に実行されます。たとえば、実行を高速化するためにテストを並列で実行することは、バージョン 5.3 以降でオプトイン機能として利用できます。並列実行を有効にするには、junit.jupiter.execution.parallel.enabled 設定パラメータを true に設定します。たとえば、junit-platform.properties で設定します(他のオプションについては、構成パラメータを参照してください)。

このプロパティを有効にすることは、テストを並列で実行するために必要な最初の手順に過ぎないことに注意してください。有効にすると、テストクラスとメソッドはデフォルトでは引き続き順番に実行されます。テストツリー内のノードが同時実行されるかどうかは、その実行モードによって制御されます。次の 2 つのモードが使用可能です。

SAME_THREAD

親で使用されているものと同じスレッドでの実行を強制します。たとえば、テストメソッドで使用した場合、テストメソッドは、包含するテストクラスの @BeforeAll または @AfterAll メソッドと同じスレッドで実行されます。

CONCURRENT

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

デフォルトでは、テストツリー内のノードは SAME_THREAD 実行モードを使用します。junit.jupiter.execution.parallel.mode.default 設定パラメータを設定することで、デフォルトを変更できます。あるいは、@Execution アノテーションを使用して、アノテーション付きの要素とそのサブ要素(もしあれば)の実行モードを変更し、個々のテストクラスごとに並列実行をアクティブにすることができます。

すべてのテストを並列で実行するための構成パラメータ
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent

デフォルトの実行モードは、いくつかの注目すべき例外を除いて、テストツリーのすべてのノードに適用されます。具体的には、Lifecycle.PER_CLASS モードまたは MethodOrdererMethodOrderer.Random を除く)を使用するテストクラスです。前者の場合、テスト作成者はテストクラスがスレッドセーフであることを保証する必要があります。後者の場合、同時実行は構成された実行順序と競合する可能性があります。したがって、どちらの場合も、そのようなテストクラスのテストメソッドは、テストクラスまたはメソッドに @Execution(CONCURRENT) アノテーションが存在する場合にのみ、同時実行されます。

並列実行が有効になっていて、デフォルトの ClassOrderer が登録されている場合(詳細については、クラスの順序を参照)、トップレベルのテストクラスは最初に応じてソートされ、その順序でスケジュールされます。ただし、実行されるスレッドは JUnit によって直接制御されないため、正確にその順序で開始されることは保証されません。

CONCURRENT 実行モードで構成されているテストツリーのすべてのノードは、提供された 構成に従って完全に並列で実行され、宣言型の 同期メカニズムに従います。 標準出力/エラーのキャプチャは別途有効にする必要があることに注意してください。

さらに、junit.jupiter.execution.parallel.mode.classes.default 設定パラメータを設定することで、トップレベルクラスのデフォルトの実行モードを構成できます。両方の構成パラメータを組み合わせることで、クラスを並列で実行し、そのメソッドを同じスレッドで実行するように構成できます。

トップレベルのクラスを並列で実行し、メソッドを同じスレッドで実行するための構成パラメータ
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = same_thread
junit.jupiter.execution.parallel.mode.classes.default = concurrent

反対の組み合わせでは、1 つのクラス内のすべてのメソッドが並列で実行されますが、トップレベルのクラスは順番に実行されます。

トップレベルのクラスを順番に実行し、そのメソッドを並列で実行するための構成パラメータ
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = same_thread

次の図は、junit.jupiter.execution.parallel.mode.defaultjunit.jupiter.execution.parallel.mode.classes.default の 4 つの組み合わせすべて(最初の列のラベルを参照)について、クラスごとに 2 つのテストメソッドを持つ 2 つのトップレベルテストクラス AB の実行がどのように動作するかを示しています。

writing tests execution mode
デフォルトの実行モード構成の組み合わせ

junit.jupiter.execution.parallel.mode.classes.default 設定パラメータが明示的に設定されていない場合、junit.jupiter.execution.parallel.mode.default の値が代わりに使用されます。

2.20.1. 構成

目的の並列度や最大プールサイズなどのプロパティは、ParallelExecutionConfigurationStrategy を使用して構成できます。JUnit Platform は、すぐに利用できる 2 つの実装を提供しています: dynamicfixed。あるいは、custom ストラテジーを実装することもできます。

ストラテジーを選択するには、junit.jupiter.execution.parallel.config.strategy 設定パラメータを次のいずれかのオプションに設定します。

dynamic

利用可能なプロセッサ/コアの数に junit.jupiter.execution.parallel.config.dynamic.factor 設定パラメータ(デフォルトは 1)を掛けた値に基づいて、目的の並列度を計算します。オプションの junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor 設定パラメータを使用して、最大スレッド数を制限できます。

fixed

必須の junit.jupiter.execution.parallel.config.fixed.parallelism 設定パラメータを、目的の並列度として使用します。オプションの junit.jupiter.execution.parallel.config.fixed.max-pool-size 設定パラメータを使用して、最大スレッド数を制限できます。

custom

目的の構成を決定するために、必須の junit.jupiter.execution.parallel.config.custom.class 設定パラメータを介して、カスタムの ParallelExecutionConfigurationStrategy 実装を指定できます。

構成ストラテジーが設定されていない場合、JUnit Jupiter は dynamic 構成ストラテジーを係数 1 で使用します。その結果、目的の並列度は、利用可能なプロセッサ/コアの数と等しくなります。

並列度だけでは、同時スレッドの最大数を意味するものではありません
デフォルトでは、JUnit Jupiter は、同時に実行されるテストの数が、構成された並列度を超えないことを保証しません。たとえば、次のセクションで説明する同期メカニズムのいずれかを使用する場合、舞台裏で使用される ForkJoinPool は、実行が十分な並列度で継続されるように追加のスレッドを生成する場合があります。このような保証が必要な場合は、Java 9 以降では、dynamicfixed、および custom ストラテジーの最大プールサイズを制御することで、同時スレッドの最大数を制限できます。
関連プロパティ

次の表に、並列実行を構成するための関連プロパティを示します。このようなプロパティの設定方法の詳細については、構成パラメータを参照してください。

プロパティ 説明 サポートされる値 デフォルト値

junit.jupiter.execution.parallel.enabled

並列テスト実行を有効にする

  • true

  • false

false

junit.jupiter.execution.parallel.mode.default

テストツリー内のノードのデフォルトの実行モード

  • concurrent

  • same_thread

same_thread

junit.jupiter.execution.parallel.mode.classes.default

トップレベルクラスのデフォルトの実行モード

  • concurrent

  • same_thread

same_thread

junit.jupiter.execution.parallel.config.strategy

目的の並列度と最大プールサイズの実行ストラテジー

  • dynamic

  • fixed

  • custom

dynamic

junit.jupiter.execution.parallel.config.dynamic.factor

dynamic 構成ストラテジーの目的の並列度を決定するために、利用可能なプロセッサ/コアの数に乗算される係数

正の小数

1.0

1

junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor

dynamic 構成ストラテジーの目的の並列度を決定するために、利用可能なプロセッサ/コアの数と junit.jupiter.execution.parallel.config.dynamic.factor の値を乗算する係数

1.0 以上の正の小数

256 + junit.jupiter.execution.parallel.config.dynamic.factor の値に利用可能なプロセッサ/コアの数を掛けたもの

junit.jupiter.execution.parallel.config.dynamic.saturate

  • true

  • false

true

dynamic 構成ストラテジーの基になるフォークジョインプールの飽和を無効にする

junit.jupiter.execution.parallel.config.fixed.parallelism

fixed 構成ストラテジーの目的の並列度

正の整数

デフォルト値なし

junit.jupiter.execution.parallel.config.fixed.max-pool-size

fixed 構成ストラテジーの基になるフォークジョインプールの目的の最大プールサイズ

junit.jupiter.execution.parallel.config.fixed.parallelism 以上の正の整数

256 + junit.jupiter.execution.parallel.config.fixed.parallelism の値

junit.jupiter.execution.parallel.config.fixed.saturate

  • true

  • false

true

fixed 構成ストラテジーの基になるフォークジョインプールの飽和を無効にする

junit.jupiter.execution.parallel.config.custom.class

custom 構成ストラテジーに使用される ParallelExecutionConfigurationStrategy の完全修飾クラス名

正の整数

例:org.example.CustomStrategy

2.20.2. 同期

以下の例のテストが、@ResourceLockを使用せずに並行して実行された場合、それらは不安定になります。同じJVMシステムプロパティへの書き込みと読み取りという固有の競合状態により、パスする場合もあれば、失敗する場合もあるでしょう。

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

テストを分離して実行する

ほとんどのテストクラスが同期なしで並行して実行できるが、分離して実行する必要があるテストクラスがいくつかある場合は、後者を@Isolatedアノテーションでマークできます。このようなクラス内のテストは、他のテストが同時に実行されることなく、順次実行されます。

共有リソースを一意に識別するStringに加えて、アクセスモードを指定できます。共有リソースへのREADアクセスを必要とする2つのテストは、互いに並行して実行できますが、同じ共有リソースへのREAD_WRITEアクセスを必要とする他のテストが実行されている間は実行できません。

@Execution(CONCURRENT)
class SharedResourcesDemo {

    private Properties backup;

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

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

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

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

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

}

2.21. ビルトイン拡張機能

JUnitチームは再利用可能な拡張機能を個別のライブラリにパッケージ化して管理することを推奨していますが、JUnit Jupiter APIアーティファクトには、ユーザーが別の依存関係を追加する必要がないほど一般的に有用であると考えられる、いくつかのユーザー向けの拡張機能実装が含まれています。

2.21.1. TempDirectory拡張機能

ビルトインのTempDirectory拡張機能は、個々のテストまたはテストクラス内のすべてのテスト用の一時ディレクトリを作成およびクリーンアップするために使用されます。これはデフォルトで登録されています。これを使用するには、型がjava.nio.file.Pathまたはjava.io.Fileのfinalでない未割り当てフィールドに@TempDirをアノテーションするか、@TempDirでアノテーションされた型がjava.nio.file.Pathまたはjava.io.Fileのパラメータをライフサイクルメソッドまたはテストメソッドに追加します。

たとえば、次のテストでは、単一のテストメソッドに@TempDirでアノテーションされたパラメータを宣言し、一時ディレクトリにファイルを作成して書き込み、その内容を確認します。

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

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

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

複数のアノテーション付きパラメータを指定することで、複数の一時ディレクトリを注入できます。

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

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

    assertNotEquals(sourceFile, targetFile);
    assertEquals(singletonList("a,b,c"), Files.readAllLines(targetFile));
}
テストクラスまたはメソッド全体で単一の一時ディレクトリを使用する古い動作に戻すには(アノテーションが使用されるレベルに応じて)、junit.jupiter.tempdir.scope構成パラメータをper_contextに設定できます。ただし、このオプションは非推奨であり、将来のリリースで削除されることに注意してください。

@TempDirはコンストラクターパラメータではサポートされていません。ライフサイクルメソッドと現在のテストメソッドで一時ディレクトリへの単一の参照を保持する場合は、インスタンスフィールドに@TempDirをアノテーションしてフィールドインジェクションを使用してください。

次の例では、共有一時ディレクトリをstaticフィールドに格納します。これにより、テストクラスのすべてのライフサイクルメソッドおよびテストメソッドで同じsharedTempDirを使用できます。より良い分離のために、各テストメソッドが個別のディレクトリを使用するように、インスタンスフィールドを使用する必要があります。

テストメソッド間で一時ディレクトリを共有するテストクラス
class SharedTempDirectoryDemo {

    @TempDir
    static Path sharedTempDir;

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

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

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

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

}

@TempDirアノテーションには、cleanup属性というオプションがあり、NEVERON_SUCCESS、またはALWAYSのいずれかに設定できます。クリーンアップモードがNEVERに設定されている場合、テスト完了後、一時ディレクトリは削除されません。ON_SUCCESSに設定されている場合、一時ディレクトリはテストが正常に完了した場合にのみ削除されます。

デフォルトのクリーンアップモードはALWAYSです。junit.jupiter.tempdir.cleanup.mode.default構成パラメータを使用して、このデフォルトを上書きできます。

クリーンアップされない一時ディレクトリを持つテストクラス
class CleanupModeDemo {

    @Test
    void fileTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) {
        // perform test
    }

}

@TempDirは、オプションのfactory属性を介して一時ディレクトリのプログラムによる作成をサポートします。これは通常、一時ディレクトリの作成を制御するために使用されます。たとえば、親ディレクトリや使用するファイルシステムを定義します。

ファクトリは、TempDirFactoryを実装することで作成できます。実装は引数なしのコンストラクターを提供する必要があり、いつ、何回インスタンス化されるかについての仮定はしないでください。ただし、それらのcreateTempDirectory(…​)メソッドとclose()メソッドは、同じスレッドからこの順序で、インスタンスごとに1回ずつ呼び出されると想定できます。

Jupiterで利用可能なデフォルトの実装では、ディレクトリ作成をjava.nio.file.Files::createTempDirectoryに委譲し、ディレクトリ名の生成に使用する接頭辞文字列としてjunitを渡します。

次の例では、junit定数値の代わりに、テスト名をディレクトリ名の接頭辞として使用するファクトリを定義します。

ディレクトリ名の接頭辞としてテスト名を持つ一時ディレクトリを持つテストクラス
class TempDirFactoryDemo {

    @Test
    void factoryTest(@TempDir(factory = Factory.class) Path tempDir) {
        assertTrue(tempDir.getFileName().toString().startsWith("factoryTest"));
    }

    static class Factory implements TempDirFactory {

        @Override
        public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext)
                throws IOException {
            return Files.createTempDirectory(extensionContext.getRequiredTestMethod().getName());
        }

    }

}

一時ディレクトリの作成にJimfsのようなインメモリファイルシステムを使用することも可能です。次の例は、それを実現する方法を示しています。

Jimfsインメモリファイルシステムで作成された一時ディレクトリを持つテストクラス
class InMemoryTempDirDemo {

    @Test
    void test(@TempDir(factory = JimfsTempDirFactory.class) Path tempDir) {
        // perform test
    }

    static class JimfsTempDirFactory implements TempDirFactory {

        private final FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());

        @Override
        public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext)
                throws IOException {
            return Files.createTempDirectory(fileSystem.getPath("/"), "junit");
        }

        @Override
        public void close() throws IOException {
            fileSystem.close();
        }

    }

}

@TempDirは、繰り返しを減らすためにメタアノテーションとしても使用できます。次のコードリストは、@TempDir(factory = JimfsTempDirFactory.class)のドロップイン代替として使用できるカスタムの@JimfsTempDirアノテーションを作成する方法を示しています。

@TempDirでメタアノテーションされたカスタムアノテーション
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@TempDir(factory = JimfsTempDirFactory.class)
@interface JimfsTempDir {
}

次の例は、カスタムの@JimfsTempDirアノテーションを使用する方法を示しています。

カスタムアノテーションを使用するテストクラス
class JimfsTempDirAnnotationDemo {

    @Test
    void test(@JimfsTempDir Path tempDir) {
        // perform test
    }

}

TempDirアノテーションが宣言されているフィールドまたはパラメータのメタアノテーションまたは追加のアノテーションは、ファクトリを構成するための追加の属性を公開する場合があります。このようなアノテーションと関連する属性は、createTempDirectoryAnnotatedElementContextパラメータを介してアクセスできます。

junit.jupiter.tempdir.factory.default構成パラメータを使用して、デフォルトで使用するTempDirFactoryの完全修飾クラス名を指定できます。@TempDirアノテーションのfactory属性を介して構成されたファクトリの場合と同様に、提供されたクラスはTempDirFactoryインターフェースを実装する必要があります。アノテーションのfactory属性で別のファクトリを指定しない限り、デフォルトのファクトリはすべての@TempDirアノテーションに使用されます。

要約すると、一時ディレクトリのファクトリは、次の優先順位ルールに従って決定されます。

  1. 存在する場合、@TempDirアノテーションのfactory属性

  2. 存在する場合、構成パラメータを介して構成されたデフォルトのTempDirFactory

  3. それ以外の場合、org.junit.jupiter.api.io.TempDirFactory$Standardが使用されます。

3. JUnit 4からの移行

JUnit Jupiterプログラミングモデルと拡張モデルは、RulesRunnersなどのJUnit 4の機能をネイティブにサポートしていませんが、JUnit Jupiterに移行するために、ソースコードのメンテナーが既存のテスト、テスト拡張機能、およびカスタムビルドテストインフラストラクチャをすべて更新する必要があるとは想定されていません。

代わりに、JUnitは、JUnit Vintageテストエンジンを介して、JUnit 3およびJUnit 4に基づいた既存のテストをJUnitプラットフォームインフラストラクチャを使用して実行できるようにする、緩やかな移行パスを提供します。JUnit Jupiterに固有のすべてのクラスとアノテーションはorg.junit.jupiterベースパッケージの下にあるため、クラスパスにJUnit 4とJUnit Jupiterの両方があっても競合は発生しません。したがって、既存のJUnit 4テストをJUnit Jupiterテストと一緒に保守しても安全です。さらに、JUnitチームはJUnit 4.xベースラインのメンテナンスとバグ修正リリースを引き続き提供するため、開発者は自分のスケジュールでJUnit Jupiterに移行するのに十分な時間があります。

3.1. JUnitプラットフォームでのJUnit 4テストの実行

junit-vintage-engineアーティファクトがテストランタイムパスにあることを確認してください。その場合、JUnit 3およびJUnit 4のテストは、JUnitプラットフォームランチャーによって自動的に選択されます。

GradleとMavenでこれを行う方法については、junit5-samplesリポジトリのサンプルプロジェクトを参照してください。

3.1.1. カテゴリサポート

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

3.2. 移行のヒント

以下は、既存のJUnit 4テストをJUnit Jupiterに移行する際に注意する必要があるトピックです。

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

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

    • org.junit.AssertまたはAssertJHamcrestTruthなどの他のアサーションライブラリのアサーションメソッドを引き続き使用できることに注意してください。

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

    • JUnit Jupiter 5.4以降のバージョンでは、JUnit 4のorg.junit.Assumeクラスのメソッドがアサンプションとしてサポートされることに注意してください。具体的には、JUnit Jupiterは、テストが失敗としてマークされるのではなく、中断されることを示すためにJUnit 4のAssumptionViolatedExceptionをサポートしています。

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

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

  • @Ignoreは存在しなくなりました。代わりに@Disabledまたは他の組み込みの実行条件のいずれかを使用してください。

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

  • @RunWithは存在しなくなりました。@ExtendWithで置き換えられます。

  • @Rule@ClassRuleは存在しなくなりました。@ExtendWith@RegisterExtensionで置き換えられます。

  • @Test(expected = …​)およびExpectedExceptionルールは存在しなくなりました。代わりにAssertions.assertThrows(…​)を使用してください。

  • JUnit Jupiterのアサーションとアサンプションは、最初の引数の代わりに最後の引数として失敗メッセージを受け入れます。

3.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のルールインポートステートメントを含めて変更せずに残すことができます。

この制限付きのRuleサポートは、クラスレベルのアノテーション@EnableRuleMigrationSupportでオンにできます。このアノテーションは構成されたアノテーションであり、すべてのルール移行サポート拡張機能(VerifierSupportExternalResourceSupport、およびExpectedExceptionSupport)を有効にします。あるいは、テストクラスに@EnableJUnit4MigrationSupportアノテーションを付けることもできます。これにより、ルールJUnit 4の@Ignoreアノテーションの移行サポートが登録されます(JUnit 4 @Ignoreサポートを参照)。

ただし、JUnit Jupiterの新しい拡張機能を開発する場合は、JUnit 4のルールベースモデルではなく、JUnit Jupiterの新しい拡張モデルを使用してください。

3.4. JUnit 4 @Ignoreサポート

JUnit 4からJUnit Jupiterへのスムーズな移行パスを提供するために、junit-jupiter-migrationsupportモジュールは、JUnit Jupiterの@Disabledアノテーションと同様に、JUnit 4の@Ignoreアノテーションのサポートを提供します。

JUnit Jupiterベースのテストで@Ignoreを使用するには、ビルドでjunit-jupiter-migrationsupportモジュールへのテスト依存関係を構成し、テストクラスに@ExtendWith(IgnoreCondition.class)または@EnableJUnit4MigrationSupport制限付きJUnit 4ルールサポートとともにIgnoreConditionを自動的に登録します)アノテーションを付けます。IgnoreConditionは、@Ignoreアノテーションが付いたテストクラスまたはテストメソッドを無効にするExecutionConditionです。

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

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

    @Ignore
    @Test
    void testWillBeIgnored() {
    }

    @Test
    void testWillBeExecuted() {
    }
}

3.5. 失敗メッセージ引数

JUnit JupiterのAssumptionsクラスとAssertionsクラスは、JUnit 4とは異なる順序で引数を宣言します。JUnit 4では、アサーションおよびアサンプションメソッドは、最初の引数として失敗メッセージを受け入れます。一方、JUnit Jupiterでは、アサーションおよびアサンプションメソッドは、最後の引数として失敗メッセージを受け入れます。

たとえば、JUnit 4のメソッドassertEqualsassertEquals(String message, Object expected, Object actual)として宣言されていますが、JUnit JupiterではassertEquals(Object expected, Object actual, String message)として宣言されています。この理由は、失敗メッセージがオプションであり、オプションの引数はメソッドシグネチャで必須引数の後に宣言する必要があるためです。

この変更の影響を受けるメソッドは次のとおりです。

  • アサーション

    • assertTrue

    • assertFalse

    • assertNull

    • assertNotNull

    • assertEquals

    • assertNotEquals

    • assertArrayEquals

    • assertSame

    • assertNotSame

    • assertThrows

  • アサンプション

    • assumeTrue

    • assumeFalse

4. テストの実行

4.1. IDEサポート

4.1.1. IntelliJ IDEA

IntelliJ IDEAは、バージョン2016.2以降、JUnit Platformでのテスト実行をサポートしています。詳細については、このIntelliJ IDEAリソースを参照してください。ただし、より新しいバージョンのIDEAでは、プロジェクトで使用されているAPIバージョンに基づいて、junit-platform-launcherjunit-jupiter-engine、およびjunit-vintage-engineのJARが自動的にダウンロードされるため、IDEA 2017.3以降を使用することをお勧めします。

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

別のJUnit 5バージョン(例:5.10.2)を使用するには、junit-platform-launcherjunit-jupiter-engine、およびjunit-vintage-engineの対応するバージョンのJARをクラスパスに含める必要がある場合があります。

追加のGradle依存関係
testImplementation(platform("org.junit:junit-bom:5.10.2"))
testRuntimeOnly("org.junit.platform:junit-platform-launcher") {
  because("Only needed to run tests in a version of IntelliJ IDEA that bundles older versions")
}
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
追加のMaven依存関係
<!-- ... -->
<dependencies>
    <!-- Only needed to run tests in a version of IntelliJ IDEA that bundles older versions -->
    <dependency>
        <groupId>org.junit.platform</groupId>
        <artifactId>junit-platform-launcher</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.vintage</groupId>
        <artifactId>junit-vintage-engine</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.junit</groupId>
            <artifactId>junit-bom</artifactId>
            <version>5.10.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

4.1.2. Eclipse

Eclipse IDEは、Eclipse Oxygen.1a(4.7.1a)リリース以降、JUnit Platformのサポートを提供しています。

EclipseでのJUnit 5の使用に関する詳細については、Eclipse Project Oxygen.1a(4.7.1a) - 新機能と注目点ドキュメントの公式のEclipseでのJUnit 5のサポートセクションを参照してください。

4.1.3. NetBeans

NetBeansは、Apache NetBeans 10.0リリース以降、JUnit JupiterとJUnit Platformのサポートを提供しています。

詳細については、Apache NetBeans 10.0リリースノートのJUnit 5セクションを参照してください。

4.1.4. Visual Studio Code

Visual Studio Codeは、Java Test Runner拡張機能(Java Extension Packの一部としてデフォルトでインストールされます)を介して、JUnit JupiterとJUnit Platformをサポートしています。

詳細については、Visual Studio CodeでのJavaドキュメントのテストセクションを参照してください。

4.1.5. その他のIDE

前のセクションにリストされている以外のエディターまたはIDEを使用している場合、JUnitチームはJUnit 5の使用を支援するための2つの代替ソリューションを提供しています。たとえば、コマンドラインからコンソールランチャーを手動で使用するか、IDEにJUnit 4の組み込みサポートがある場合は、JUnit 4ベースのランナーでテストを実行できます。

4.2. ビルドサポート

4.2.1. Gradle

バージョン4.6以降、GradleはJUnit Platformでテストを実行するためのネイティブサポートを提供しています。これを有効にするには、build.gradletestタスク宣言内でuseJUnitPlatform()を指定する必要があります。

test {
    useJUnitPlatform()
}

タグタグ式、またはエンジンによるフィルタリングもサポートされています。

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

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

依存関係のバージョンの調整

Spring Bootを使用していない限り、JUnit Platform BOMを使用して、すべてのJUnit 5アーティファクトのバージョンを調整することをお勧めします。Spring Bootは独自の依存関係管理方法を定義します。

dependencies {
    testImplementation(platform("org.junit:junit-bom:5.10.2"))
}

BOMを使用すると、org.junit.platformorg.junit.jupiter、およびorg.junit.vintageグループIDを持つすべてのアーティファクトへの依存関係を宣言するときに、バージョンを省略できます。

Spring Bootアプリケーションで使用されるJUnitのバージョンをオーバーライドする方法の詳細については、Spring Bootを参照してください。
構成パラメーター

標準のGradle testタスクは、現在、テストの検出と実行に影響を与えるJUnit Platform構成パラメーターを設定するための専用のDSLを提供していません。ただし、システムプロパティ(以下に示す)またはjunit-platform.propertiesファイルを介して、ビルドスクリプト内で構成パラメーターを提供できます。

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

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

JUnit Jupiterベースのテストのサポートを構成するには、次のような、依存関係を集約するJUnit JupiterアーティファクトへのtestImplementation依存関係を構成します。

dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") // version can be omitted when using the BOM
}

JUnit Platformは、JUnit 4へのtestImplementation依存関係と、JUnit Vintage TestEngine実装へのtestRuntimeOnly依存関係を次のように構成する限り、JUnit 4ベースのテストを実行できます。

dependencies {
    testImplementation("junit:junit:4.13.2")
    testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.10.2") // version can be omitted when using the BOM
}
ロギングの構成(オプション)

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 to SLF4J Bridgeを使用できます。

4.2.2. Maven

バージョン2.22.0以降、Maven SurefireとMaven FailsafeはJUnit Platformでテストを実行するためのネイティブサポートを提供しています。junit5-jupiter-starter-mavenプロジェクトのpom.xmlファイルは、Maven Surefireプラグインを使用する方法を示しており、Mavenビルドを構成するための出発点として役立ちます。

相互運用性の問題を回避するには、Maven Surefire/Failsafe 3.0.0-M4以降を使用してください。

Maven Surefire/Failsafe 3.0.0-M4では、テストランタイムクラスパスにあるJUnit Platformバージョンと使用するJUnit Platform Launcherのバージョンを調整するためのサポートが導入されました。したがって、相互運用性の問題を回避するには、バージョン3.0.0-M4以降を使用することをお勧めします。

あるいは、次のように、Mavenビルドに一致するバージョンのJUnit Platform Launcherへのテスト依存関係を追加できます。

<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-launcher</artifactId>
    <version>1.10.2</version>
    <scope>test</scope>
</dependency>
依存関係のバージョンの調整

Spring Bootを使用していない限り、JUnit Platform BOMを使用して、すべてのJUnit 5アーティファクトのバージョンを調整することをお勧めします。Spring Bootは独自の依存関係管理方法を定義します。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.junit</groupId>
            <artifactId>junit-bom</artifactId>
            <version>5.10.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

BOMを使用すると、org.junit.platformorg.junit.jupiter、およびorg.junit.vintageグループIDを持つすべてのアーティファクトへの依存関係を宣言するときに、バージョンを省略できます。

Spring Bootアプリケーションで使用されるJUnitのバージョンをオーバーライドする方法の詳細については、Spring Bootを参照してください。
テストエンジンの構成

Maven SurefireまたはMaven Failsafeでテストをまったく実行するには、少なくとも1つのTestEngine実装をテストクラスパスに追加する必要があります。

JUnit Jupiterベースのテストのサポートを構成するには、次のような、JUnit Jupiter APIおよびJUnit Jupiter TestEngine実装へのtestスコープの依存関係を構成します。

<!-- ... -->
<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.2</version> <!-- can be omitted when using the BOM -->
        <scope>test</scope>
    </dependency>
    <!-- ... -->
</dependencies>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.1.2</version>
        </plugin>
        <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>3.1.2</version>
        </plugin>
    </plugins>
</build>
<!-- ... -->

Maven SurefireおよびMaven Failsafeは、次のようにJUnit 4およびJUnit Vintage TestEngine実装へのtestスコープの依存関係を構成する限り、JupiterテストとともにJUnit 4ベースのテストを実行できます。

<!-- ... -->
<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.vintage</groupId>
        <artifactId>junit-vintage-engine</artifactId>
        <version>5.10.2</version> <!-- can be omitted when using the BOM -->
        <scope>test</scope>
    </dependency>
    <!-- ... -->
</dependencies>
<!-- ... -->
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.1.2</version>
        </plugin>
        <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>3.1.2</version>
        </plugin>
    </plugins>
</build>
<!-- ... -->
テストクラス名によるフィルタリング

Maven Surefire Plugin は、完全修飾名が以下のパターンに一致するテストクラスをスキャンします。

  • **/Test*.java

  • **/*Test.java

  • **/*Tests.java

  • **/*TestCase.java

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

ただし、pom.xml ファイルで明示的な include および exclude ルールを設定することにより、このデフォルトの動作をオーバーライドできることに注意してください。たとえば、Maven Surefire が静的メンバークラスを除外しないようにするには、次のように除外ルールをオーバーライドできます。

Maven Surefire の除外ルールのオーバーライド
<!-- ... -->
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.1.2</version>
            <configuration>
                <excludes>
                    <exclude/>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>
<!-- ... -->

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

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

以下の構成プロパティを使用して、タグまたはタグ式でテストをフィルタリングできます。

  • タグまたはタグ式を含めるには、groups を使用します。

  • タグまたはタグ式を除外するには、excludedGroups を使用します。

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

Java Properties ファイルの構文(以下に示す)または junit-platform.properties ファイルを使用してキーと値のペアを指定することにより、JUnit Platform の構成パラメーターを設定して、テストの検出と実行に影響を与えることができます。

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

4.2.3. Ant

バージョン 1.10.3 以降、Ant には、JUnit Platform でのテストの起動をネイティブでサポートするjunitlauncherタスクがあります。junitlauncherタスクは、JUnit Platform を起動し、選択したテストのコレクションを渡すことのみを担当します。その後、JUnit Platform は登録されたテストエンジンに委任して、テストを検出および実行します。

junitlauncherタスクは、ユーザーがテストエンジンで実行したいテストを選択できるように、リソースコレクションなどのネイティブな Ant 構造に可能な限り近づくように努めています。これにより、他の多くのコア Ant タスクと比較して、タスクは一貫性があり自然な感じになります。

Ant のバージョン 1.10.6 以降、junitlauncher タスクは別の JVM でのテストのフォークをサポートします。

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 要素を使用すると、異なる場所にある複数のテストクラスを選択できます。

使用法と構成オプションの詳細については、junitlauncher タスクの公式 Ant ドキュメントを参照してください。

4.2.4. Spring Boot

Spring Boot は、プロジェクトで使用する JUnit のバージョンを管理するための自動サポートを提供します。さらに、spring-boot-starter-test アーティファクトには、JUnit Jupiter、AssertJ、Mockito などのテストライブラリが自動的に含まれています。

ビルドが Spring Boot の依存関係管理サポートに依存している場合は、junit-bom をビルドスクリプトにインポートしないでください。これにより、JUnit 依存関係の重複した(および潜在的に競合する)管理が発生します。

Spring Boot アプリケーションで使用されている依存関係のバージョンをオーバーライドする必要がある場合は、Spring Boot プラグインで使用される BOM で定義されているバージョンプロパティの正確な名前をオーバーライドする必要があります。たとえば、Spring Boot の JUnit Jupiter バージョンプロパティの名前は junit-jupiter.version です。依存関係のバージョンを変更するメカニズムは、GradleMaven の両方でドキュメント化されています。

Gradle では、build.gradle ファイルに以下を含めることで、JUnit Jupiter のバージョンをオーバーライドできます。

ext['junit-jupiter.version'] = '5.10.2'

Maven では、pom.xml ファイルに以下を含めることで、JUnit Jupiter のバージョンをオーバーライドできます。

<properties>
    <junit-jupiter.version>5.10.2</junit-jupiter.version>
</properties>

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

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

すべての依存関係を含む実行可能な junit-platform-console-standalone-1.10.2.jar は、Maven Central リポジトリのjunit-platform-console-standalone ディレクトリに公開されています。これには、次の依存関係が含まれています

  • junit:junit:4.13.2

  • org.apiguardian:apiguardian-api:1.1.2

  • org.hamcrest:hamcrest-core:1.3

  • org.junit.jupiter:junit-jupiter-api:5.10.2

  • org.junit.jupiter:junit-jupiter-engine:5.10.2

  • org.junit.jupiter:junit-jupiter-params:5.10.2

  • org.junit.platform:junit-platform-commons:1.10.2

  • org.junit.platform:junit-platform-console:1.10.2

  • org.junit.platform:junit-platform-engine:1.10.2

  • org.junit.platform:junit-platform-launcher:1.10.2

  • org.junit.platform:junit-platform-reporting:1.10.2

  • org.junit.platform:junit-platform-suite-api:1.10.2

  • org.junit.platform:junit-platform-suite-commons:1.10.2

  • org.junit.platform:junit-platform-suite-engine:1.10.2

  • org.junit.platform:junit-platform-suite:1.10.2

  • org.junit.vintage:junit-vintage-engine:5.10.2

  • org.opentest4j:opentest4j:1.3.0

以下に示すように、スタンドアロンの ConsoleLauncher実行できます。

$ java -jar junit-platform-console-standalone-1.10.2.jar execute <OPTIONS>

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

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

また、以下に示すように(たとえば、ディレクトリ内のすべての jar を含めるために)スタンドアロンの ConsoleLauncher を実行することもできます。

$ java -cp classes:testlib/* org.junit.platform.console.ConsoleLauncher <OPTIONS>
終了コード
ConsoleLauncher は、コンテナーまたはテストのいずれかが失敗した場合、ステータスコード 1 で終了します。テストが検出されず、--fail-if-no-tests コマンドラインオプションが指定されている場合、ConsoleLauncher はステータスコード 2 で終了します。それ以外の場合、終了コードは 0 です。

4.3.1. サブコマンドとオプション

ConsoleLauncher は、次のサブコマンドを提供します

Usage: junit [OPTIONS] [COMMAND]
Launches the JUnit Platform for test discovery and execution.
      [@<filename>...]   One or more argument files containing options.
Commands:
  discover  Discover tests
  execute   Execute tests
  engines   List available test engines

For more information, please refer to the JUnit User Guide at
https://junit.dokyumento.jp/junit5/docs/current/user-guide/
テストの検出
Usage: junit discover [OPTIONS]
Discover tests
      [@<filename>...]       One or more argument files containing options.
      --disable-banner       Disable print out of the welcome message.
      --disable-ansi-colors  Disable ANSI colors in output (not supported by all terminals).
  -h, --help                 Display help information.

SELECTORS

      --scan-classpath, --scan-class-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.
      --scan-modules         Scan all resolved modules for test discovery.
  -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.
  -o, --select-module=NAME   Select single module 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.
  -i, --select-iteration=TYPE:VALUE[INDEX(..INDEX)?(,INDEX(..INDEX)?)*]
                             Select iterations for test discovery (e.g. method:com.acme.Foo#m()
                               [1..2]). This option can be repeated.

FILTERS

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

RUNTIME CONFIGURATION

      -cp, --classpath, --class-path=PATH
                             Provide additional classpath entries -- for example, for adding
                               engines and their dependencies. This option can be repeated.
      --config=KEY=VALUE     Set a configuration parameter for test discovery and execution.
                               This option can be repeated.

CONSOLE OUTPUT

      --color-palette=FILE   Specify a path to a properties file to customize ANSI style of
                               output (not supported by all terminals).
      --single-color         Style test output using only text attributes, no color (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, testfeed. 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 is detected based on
                               default character encoding.

For more information, please refer to the JUnit User Guide at
https://junit.dokyumento.jp/junit5/docs/current/user-guide/
テストの実行
Usage: junit execute [OPTIONS]
Execute tests
      [@<filename>...]       One or more argument files containing options.
      --disable-banner       Disable print out of the welcome message.
      --disable-ansi-colors  Disable ANSI colors in output (not supported by all terminals).
  -h, --help                 Display help information.

SELECTORS

      --scan-classpath, --scan-class-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.
      --scan-modules         Scan all resolved modules for test discovery.
  -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.
  -o, --select-module=NAME   Select single module 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.
  -i, --select-iteration=TYPE:VALUE[INDEX(..INDEX)?(,INDEX(..INDEX)?)*]
                             Select iterations for test discovery (e.g. method:com.acme.Foo#m()
                               [1..2]). This option can be repeated.

FILTERS

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

RUNTIME CONFIGURATION

      -cp, --classpath, --class-path=PATH
                             Provide additional classpath entries -- for example, for adding
                               engines and their dependencies. This option can be repeated.
      --config=KEY=VALUE     Set a configuration parameter for test discovery and execution.
                               This option can be repeated.

CONSOLE OUTPUT

      --color-palette=FILE   Specify a path to a properties file to customize ANSI style of
                               output (not supported by all terminals).
      --single-color         Style test output using only text attributes, no color (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, testfeed. 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 is detected based on
                               default character encoding.

REPORTING

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

For more information, please refer to the JUnit User Guide at
https://junit.dokyumento.jp/junit5/docs/current/user-guide/
テストエンジンのリスト
Usage: junit engines [OPTIONS]
List available test engines
      [@<filename>...]   One or more argument files containing options.
      --disable-banner   Disable print out of the welcome message.
      --disable-ansi-colors
                         Disable ANSI colors in output (not supported by all terminals).
  -h, --help             Display help information.

For more information, please refer to the JUnit User Guide at
https://junit.dokyumento.jp/junit5/docs/current/user-guide/

4.3.2. 引数ファイル(@-files)

一部のプラットフォームでは、多数のオプションまたは長い引数を使用してコマンドラインを作成する場合、コマンドラインの長さに関するシステム制限に遭遇する可能性があります。

バージョン 1.3 以降、ConsoleLauncher は、引数ファイル@-files とも呼ばれる)をサポートします。引数ファイルは、コマンドに渡される引数を格納するファイルです。基盤となるpicocliコマンドラインパーサーが @ 文字で始まる引数を検出すると、そのファイルの内容を引数リストに展開します。

ファイル内の引数は、スペースまたは改行で区切ることができます。引数に埋め込まれた空白が含まれている場合は、引数全体を二重引用符または一重引用符で囲む必要があります(例:"-f=My Files/Stuff.java")。

引数ファイルが存在しないか、読み取れない場合、引数は文字どおりに処理され、削除されません。これにより、「一致しない引数」エラーメッセージが表示される可能性があります。picocli.trace システムプロパティを DEBUG に設定してコマンドを実行すると、このようなエラーのトラブルシューティングを行うことができます。

複数の @-files をコマンドラインで指定できます。指定されたパスは、現在のディレクトリに対する相対パスまたは絶対パスにすることができます。

先頭に @ 文字を持つ実際のパラメーターは、追加の @ 記号でエスケープすることで渡すことができます。たとえば、@@somearg@somearg になり、展開の対象にはなりません。

4.3.3. 色のカスタマイズ

ConsoleLauncher の出力で使用される色はカスタマイズできます。オプション --single-color は、組み込みのモノクロスタイルを適用し、--color-palette は、ANSI SGR カラー スタイルをオーバーライドするためのプロパティファイルを受け入れます。以下のプロパティファイルは、デフォルトのスタイルを示しています

SUCCESSFUL = 32
ABORTED = 33
FAILED = 31
SKIPPED = 35
CONTAINER = 35
TEST = 34
DYNAMIC = 35
REPORTED = 37

4.4. JUnit 4 を使用して JUnit Platform を実行する

JUnitPlatform ランナーは非推奨になりました

JUnitPlatform ランナーは、JUnit 4 環境で JUnit Platform でテストスイートとテストを実行するための中間的なソリューションとして JUnit チームによって開発されました。

近年、すべての主流のビルドツールと IDE は、JUnit Platform で直接テストを実行するための組み込みサポートを提供しています。

さらに、junit-platform-suite-engine モジュールによって提供される @Suite サポートの導入により、JUnitPlatform ランナーは時代遅れになっています。詳細については、JUnit Platform Suite Engine を参照してください。

したがって、JUnitPlatform ランナーと @UseTechnicalNames アノテーションは、JUnit Platform 1.8 で非推奨になり、JUnit Platform 2.0 で削除されます。

JUnitPlatform ランナーを使用している場合は、@Suite サポートに移行してください。

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

クラスに @RunWith(JUnitPlatform.class) アノテーションを付けると、JUnit 4 をサポートしているが、JUnit Platform を直接サポートしていない IDE およびビルドシステムで実行できます。

JUnit Platform には JUnit 4 にない機能があるため、ランナーは JUnit Platform 機能のサブセット、特にレポートに関してのみサポートできます(表示名と技術名を参照)。

4.4.1. セットアップ

クラスパスには、次のアーティファクトとその依存関係が必要です。グループ ID、アーティファクト ID、およびバージョンに関する詳細については、依存関係メタデータを参照してください。

明示的な依存関係
  • test スコープのjunit-platform-runner: JUnitPlatform ランナーの場所

  • test スコープのjunit-4.13.2.jar: JUnit 4 を使用してテストを実行するため

  • test スコープのjunit-jupiter-api: @Test などを含む、JUnit Jupiter を使用してテストを作成するための API

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

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

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

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

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

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

  • test スコープのopentest4j

4.4.2. 表示名 vs. 技術名

@RunWith(JUnitPlatform.class) を介して実行されるクラスのカスタム表示名を定義するには、クラスに @SuiteDisplayName をアノテーションし、カスタム値を指定します。

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

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

4.4.3. 単一テストクラス

JUnitPlatform ランナーを使用する1つの方法は、テストクラスに直接 @RunWith(JUnitPlatform.class) アノテーションを付けることです。次の例のテストメソッドには、org.junit.Test(JUnit 4)ではなく、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.runner.RunWith;

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

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

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

}

4.4.4. テストスイート

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

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

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

JUnitPlatformSuiteDemo は、example パッケージとそのサブパッケージ内のすべてのテストを検出して実行します。デフォルトでは、名前が Test で始まるか、Test または Tests で終わるテストクラスのみが含まれます。

追加の構成オプション
テストの検出とフィルタリングには、@SelectPackages だけではなく、さらに多くの構成オプションがあります。詳細については、org.junit.platform.suite.api パッケージの Javadoc を参照してください。
@RunWith(JUnitPlatform.class) でアノテーションされたテストクラスとスイートは、JUnit Platform で直接(または一部の IDE でドキュメント化されている「JUnit 5」テストとして)実行**できません**。このようなクラスとスイートは、JUnit 4 インフラストラクチャを使用してのみ実行できます。

4.5. 構成パラメータ

プラットフォームにどのテストクラスとテストエンジンを含めるか、どのパッケージをスキャンするかなどを指示することに加えて、特定のテストエンジン、リスナー、または登録された拡張機能に固有の追加のカスタム構成パラメータを提供する必要がある場合があります。たとえば、JUnit Jupiter TestEngine は、次のユースケースに対する構成パラメータをサポートしています。

構成パラメータは、JUnit Platform で実行されているテストエンジンに、次のいずれかのメカニズムを介して提供できるテキストベースのキーと値のペアです。

  1. Launcher API に提供されるリクエストの構築に使用される LauncherDiscoveryRequestBuilderconfigurationParameter() および configurationParameters() メソッド。JUnit Platform によって提供されるツールのいずれかを介してテストを実行する場合、構成パラメータは次のように指定できます。

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

  3. JUnit Platform 構成ファイル:Java の Properties ファイルの構文規則に従うクラスパスのルートにある junit-platform.properties という名前のファイル。

構成パラメータは、上記で定義された正確な順序で検索されます。したがって、Launcher に直接提供される構成パラメータは、システムプロパティおよび構成ファイルを介して提供される構成パラメータよりも優先されます。同様に、システムプロパティを介して提供される構成パラメータは、構成ファイルを介して提供される構成パラメータよりも優先されます。

4.5.1. パターンマッチング構文

このセクションでは、次の機能に使用される構成パラメータに適用されるパターンマッチング構文について説明します。

指定された構成パラメータの値がアスタリスク (*) のみで構成されている場合、パターンはすべての候補クラスに対して照合されます。それ以外の場合、値は、各パターンが各候補クラスの完全修飾クラス名(*FQCN*)に対して照合される、コンマ区切りのパターンリストとして扱われます。パターン内の任意のドット (.) は、FQCN 内のドット (.) またはドル記号 ($) に対して照合されます。アスタリスク (*) は、FQCN 内の1つ以上の文字に対して照合されます。パターン内の他のすべての文字は、FQCN に対して1対1で照合されます。

  • *: すべての候補クラスに一致します。

  • org.junit.*: org.junit ベースパッケージおよびそのサブパッケージの下にあるすべての候補クラスに一致します。

  • *.MyCustomImpl: 単純クラス名が正確に MyCustomImpl であるすべての候補クラスに一致します。

  • *System*: FQCN に System が含まれるすべての候補クラスに一致します。

  • *System*, *Unit*: FQCN に System または Unit が含まれるすべての候補クラスに一致します。

  • org.example.MyCustomImpl: FQCN が正確に org.example.MyCustomImpl である候補クラスに一致します。

  • org.example.MyCustomImpl, org.example.TheirCustomImpl: FQCN が正確に org.example.MyCustomImpl または org.example.TheirCustomImpl である候補クラスに一致します。

4.6. タグ

タグは、テストのマークとフィルタリングのための JUnit Platform の概念です。コンテナとテストにタグを追加するためのプログラミングモデルは、テストフレームワークによって定義されます。たとえば、JUnit Jupiter ベースのテストでは、@Tag アノテーション(タグ付けとフィルタリングを参照)を使用する必要があります。JUnit 4 ベースのテストの場合、Vintage エンジンは @Category アノテーションをタグにマッピングします(カテゴリサポートを参照)。他のテストフレームワークでは、ユーザーがタグを指定するための独自のアノテーションまたはその他の手段を定義できます。

4.6.1. タグの構文規則

タグがどのように指定されているかに関係なく、JUnit Platform は次のルールを適用します。

  • タグは null または空白であってはなりません。

  • トリミングされたタグに空白を含めてはいけません。

  • トリミングされたタグに ISO 制御文字を含めてはいけません。

  • トリミングされたタグに、次の予約文字を含めてはいけません。

    • ,: コンマ

    • (: 左括弧

    • ): 右括弧

    • &: アンパサンド

    • |: 縦棒

    • !: 感嘆符

上記のコンテキストでは、「トリミングされた」とは、先頭と末尾の空白文字が削除されたことを意味します。

4.6.2. タグ式

タグ式は、演算子 !& および | を含むブール式です。さらに、( および ) を使用して演算子の優先順位を調整できます。

2つの特別な式、any()none() がサポートされています。これらは、タグが1つでも付いているすべてのテスト、およびタグがまったく付いていないすべてのテストをそれぞれ選択します。これらの特別な式は、通常のタグと同様に、他の式と組み合わせることができます。

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

!

not

&

and

|

or

複数の次元にわたってテストにタグを付けている場合、タグ式は、実行するテストを選択するのに役立ちます。テストタイプ(例:マイクロ統合エンドツーエンド)と機能(例:製品カタログ配送)でタグ付けする場合、次のタグ式が役立ちます。

タグ式 選択

product

製品のすべてのテスト

catalog | shipping

カタログのすべてのテストと配送のすべてのテスト

catalog & shipping

カタログ配送の間の共通部分のすべてのテスト

product & !end-to-end

製品のすべてのテスト(ただし、*エンドツーエンド*テストを除く)

(micro | integration) & (product | shipping)

製品または配送のすべてのマイクロテストまたは統合テスト

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

バージョン1.3以降、JUnit Platform は System.out および System.err に出力された出力をキャプチャするためのオプトインサポートを提供します。有効にするには、junit.platform.output.capture.stdout および/または junit.platform.output.capture.stderr構成パラメータtrue に設定します。さらに、junit.platform.output.capture.maxBuffer を使用して、実行されるテストまたはコンテナごとに使用するバッファリングされた最大バイト数を構成できます。

有効にすると、JUnit Platform は対応する出力をキャプチャし、テストまたはコンテナが終了したとレポートする直前に、登録されたすべての TestExecutionListener インスタンスに stdout または stderr キーを使用してレポートエントリとして公開します。

キャプチャされた出力には、コンテナまたはテストを実行するために使用されたスレッドによって出力された出力のみが含まれることに注意してください。テストを並行して実行する場合、特に他のスレッドによる出力は特定のテストまたはコンテナに帰属させることができないため、省略されます。

4.8. リスナーとインターセプターの使用

JUnit Platform は、TestPlan の検出と実行中のさまざまな時点で発生するイベントに JUnit、サードパーティ、およびカスタムユーザーコードが応答できるようにする、次のリスナー API を提供します。

LauncherSessionListener API は通常、ビルドツールまたは IDE によって実装され、ビルドツールまたは IDE の一部の機能をサポートするために自動的に登録されます。

LauncherDiscoveryListener および TestExecutionListener API は、レポートを作成したり、IDE にテストプランのグラフィカル表現を表示したりするために実装されることがよくあります。このようなリスナーは、ビルドツールまたは IDE によって実装および自動的に登録されたり、サードパーティライブラリに含まれたりして、自動的に登録される可能性があります。独自のリスナーを実装して登録することもできます。

リスナーの登録と構成の詳細については、このガイドの次のセクションを参照してください。

JUnit Platform は、テストスイートで使用できる以下のリスナーを提供しています。

JUnit Platform のレポート機能

LegacyXmlReportGeneratingListener は、コンソールランチャー経由で使用するか、手動で登録して、JUnit 4 ベースのテストレポートの事実上の標準と互換性のある XML レポートを生成できます。

OpenTestReportGeneratingListener は、Open Test Reporting で指定されたイベントベース形式の XML レポートを生成します。自動登録され、設定パラメータ を使用して有効化および設定できます。

詳細については、JUnit Platform のレポート機能 を参照してください。

Flight Recorder のサポート

テストの検出と実行中に Java Flight Recorder イベントを生成する FlightRecordingExecutionListener および FlightRecordingDiscoveryListener

LoggingListener

Throwable および Supplier<String> を消費する BiConsumer を介して、すべてのイベントに関する情報メッセージをログに記録するための TestExecutionListener

SummaryGeneratingListener

PrintWriter を介して印刷できるテスト実行の概要を生成する TestExecutionListener

UniqueIdTrackingListener

TestPlan の実行中にスキップまたは実行されたすべてのテストの一意の ID を追跡し、TestPlan の実行が完了したら一意の ID を含むファイルを生成する TestExecutionListener

4.8.1. Flight Recorder のサポート

バージョン 1.7 以降、JUnit Platform は Flight Recorder イベントを生成するためのオプトインサポートを提供します。JEP 328 では、Java Flight Recorder (JFR) を次のように説明しています。

Flight Recorder は、アプリケーション、JVM、および OS から発生するイベントを記録します。イベントは単一のファイルに保存され、バグレポートに添付したり、サポートエンジニアが調べたりすることができ、問題発生までの期間における問題の事後分析が可能になります。

テストの実行中に生成された Flight Recorder イベントを記録するには、次の操作が必要です。

  1. Java 8 Update 262 以降、または Java 11 以降を使用していることを確認します。

  2. テスト実行時にクラスパスまたはモジュールパスに org.junit.platform.jfr モジュール (junit-platform-jfr-1.10.2.jar) を提供します。

  3. テスト実行を開始するときに Flight Recorder を開始します。Flight Recorder は、Java コマンドラインオプションを介して開始できます。

    -XX:StartFlightRecording:filename=...

適切なコマンドについては、ビルドツールのマニュアルを参照してください。

記録されたイベントを分析するには、最近の JDK に付属している jfr コマンドラインツールを使用するか、JDK Mission Control で記録ファイルを開きます。

Flight Recorder のサポートは現在、実験的な機能です。ぜひ試してみて、JUnit チームにフィードバックを提供してください。これにより、この機能を改善し、最終的に プロモーション することができます。

4.9. スタックトレースの剪定

バージョン 1.10 以降、JUnit Platform は、失敗したテストによって生成されたスタックトレースを剪定するための組み込みサポートを提供します。この機能はデフォルトで有効になっていますが、junit.platform.stacktrace.pruning.enabled 構成パラメータfalse に設定すると無効にできます。

有効にすると、テスト自体またはその祖先の後に呼び出しが発生しない限り、org.junitjdk.internal.reflect、および sun.reflect パッケージからのすべての呼び出しはスタックトレースから削除されます。そのため、org.junit.jupiter.api.Assertions または org.junit.jupiter.api.Assumptions の呼び出しは決して除外されません。

さらに、JUnit Platform Launcher からの最初の呼び出し以前のすべての要素が削除されます。

5. 拡張モデル

5.1. 概要

JUnit 4 の競合する RunnerTestRule、および MethodRule 拡張ポイントとは対照的に、JUnit Jupiter 拡張モデルは、単一の首尾一貫した概念である Extension API で構成されています。ただし、Extension 自体は単なるマーカーインターフェースであることに注意してください。

5.2. 拡張機能の登録

拡張機能は、@ExtendWith を介して宣言的に@RegisterExtension を介してプログラムで、または Java の ServiceLoader メカニズムを介して自動的に登録できます。

5.2.1. 宣言的拡張機能登録

開発者は、テストインターフェース、テストクラス、テストメソッド、またはカスタムの合成アノテーション@ExtendWith(…​) をアノテーションし、登録する拡張機能のクラス参照を提供することにより、1 つ以上の拡張機能を宣言的に登録できます。JUnit Jupiter 5.8 以降、@ExtendWith は、フィールド、またはテストクラスのコンストラクタ、テストメソッド、および @BeforeAll@AfterAll@BeforeEach@AfterEach ライフサイクルメソッドのパラメータにも宣言できます。

たとえば、特定のテストメソッドに WebServerExtension を登録するには、次のようにテストメソッドにアノテーションを付けます。WebServerExtension はローカル Web サーバーを起動し、@WebServerUrl でアノテーションが付けられたパラメータにサーバーの URL を挿入すると仮定します。

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

特定のクラスとそのサブクラス内のすべてのテストに WebServerExtension を登録するには、次のようにテストクラスにアノテーションを付けます。

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

複数の拡張機能をこのようにまとめて登録できます

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

代替として、複数の拡張機能をこのように個別に登録できます

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

クラスレベル、メソッドレベル、またはパラメータレベルで @ExtendWith を介して宣言的に登録された拡張機能は、ソースコードで宣言された順序で実行されます。たとえば、MyFirstTestsMySecondTests の両方のテストの実行は、DatabaseExtensionWebServerExtension によって、まさにその順序で拡張されます。

複数の拡張機能を再利用可能な方法で組み合わせる場合は、カスタムの合成アノテーションを定義し、次のコードリストのように @ExtendWithメタアノテーションとして使用できます。次に、@DatabaseAndWebServerExtension@ExtendWith({ DatabaseExtension.class, WebServerExtension.class }) の代わりに使用できます。

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

上記の例では、@ExtendWith をクラスレベルまたはメソッドレベルで適用する方法を示しています。ただし、特定のユースケースでは、拡張機能をフィールドまたはパラメータレベルで宣言的に登録することが理にかなっています。コンストラクタ、テストメソッド、またはライフサイクルメソッドのフィールドまたはパラメータに挿入できる乱数を生成する RandomNumberExtension を考えてみましょう。拡張機能が @ExtendWith(RandomNumberExtension.class) でメタアノテーションが付けられた @Random アノテーションを提供する場合 (以下のリストを参照)、拡張機能は次の RandomNumberDemo の例のように透過的に使用できます。

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

    // Use static randomNumber0 field anywhere in the test class,
    // including @BeforeAll or @AfterEach lifecycle methods.
    @Random
    private static Integer randomNumber0;

    // Use randomNumber1 field in test methods and @BeforeEach
    // or @AfterEach lifecycle methods.
    @Random
    private int randomNumber1;

    RandomNumberDemo(@Random int randomNumber2) {
        // Use randomNumber2 in constructor.
    }

    @BeforeEach
    void beforeEach(@Random int randomNumber3) {
        // Use randomNumber3 in @BeforeEach method.
    }

    @Test
    void test(@Random int randomNumber4) {
        // Use randomNumber4 in test method.
    }

}

次のコードリストは、このような RandomNumberExtension の実装方法を示す例です。この実装は RandomNumberDemo のユースケースで機能しますが、すべてのユースケースをカバーするのに十分な堅牢性がない可能性があります。たとえば、乱数生成のサポートは整数に限定されています。java.security.SecureRandom の代わりに java.util.Random を使用しています。いずれにせよ、どの拡張 API が実装され、どのような理由で使用されているかに注意することが重要です。

具体的には、RandomNumberExtension は次の拡張 API を実装します

  • BeforeAllCallback: 静的フィールドの挿入をサポートするため

  • BeforeEachCallback: 非静的フィールドの挿入をサポートするため

  • ParameterResolver: コンストラクタとメソッドの挿入をサポートするため

理想的には、RandomNumberExtension は、テストクラスがインスタンス化された直後に非静的フィールドの挿入をサポートするために、BeforeEachCallback の代わりに TestInstancePostProcessor を実装します。

ただし、JUnit Jupiter では現在、非静的フィールドで @ExtendWith を介して TestInstancePostProcessor を登録することはできません (issue 3437 を参照)。それを考慮して、RandomNumberExtension は代替アプローチとして BeforeEachCallback を実装します。

import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields;

import java.lang.reflect.Field;
import java.util.function.Predicate;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.platform.commons.support.ModifierSupport;

class RandomNumberExtension
        implements BeforeAllCallback, BeforeEachCallback, ParameterResolver {

    private final java.util.Random random = new java.util.Random(System.nanoTime());

    /**
     * Inject a random integer into static fields that are annotated with
     * {@code @Random} and can be assigned an integer value.
     */
    @Override
    public void beforeAll(ExtensionContext context) {
        Class<?> testClass = context.getRequiredTestClass();
        injectFields(testClass, null, ModifierSupport::isStatic);
    }

    /**
     * Inject a random integer into non-static fields that are annotated with
     * {@code @Random} and can be assigned an integer value.
     */
    @Override
    public void beforeEach(ExtensionContext context) {
        Class<?> testClass = context.getRequiredTestClass();
        Object testInstance = context.getRequiredTestInstance();
        injectFields(testClass, testInstance, ModifierSupport::isNotStatic);
    }

    /**
     * Determine if the parameter is annotated with {@code @Random} and can be
     * assigned an integer value.
     */
    @Override
    public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) {
        return pc.isAnnotated(Random.class) && isInteger(pc.getParameter().getType());
    }

    /**
     * Resolve a random integer.
     */
    @Override
    public Integer resolveParameter(ParameterContext pc, ExtensionContext ec) {
        return this.random.nextInt();
    }

    private void injectFields(Class<?> testClass, Object testInstance,
            Predicate<Field> predicate) {

        predicate = predicate.and(field -> isInteger(field.getType()));
        findAnnotatedFields(testClass, Random.class, predicate)
            .forEach(field -> {
                try {
                    field.setAccessible(true);
                    field.set(testInstance, this.random.nextInt());
                }
                catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            });
    }

    private static boolean isInteger(Class<?> type) {
        return type == Integer.class || type == int.class;
    }

}
フィールドの @ExtendWith の拡張機能登録順序

フィールドで @ExtendWith を介して宣言的に登録された拡張機能は、決定論的ではあるが、意図的にわかりにくいアルゴリズムを使用して、@RegisterExtension フィールドと他の @ExtendWith フィールドに対して相対的に順序付けられます。ただし、@ExtendWith フィールドは @Order アノテーションを使用して順序付けできます。詳細については、@RegisterExtension フィールドの 拡張機能の登録順序 のヒントを参照してください。

@ExtendWith フィールドは、static または非静的のいずれかになります。@RegisterExtension フィールドの 静的フィールドインスタンスフィールド に関するドキュメントも @ExtendWith フィールドに適用されます。

5.2.2. プログラムによる拡張機能登録

開発者は、テストクラスのフィールドに @RegisterExtension をアノテーションすることにより、拡張機能をプログラムで登録できます。

拡張機能が @ExtendWith を介して宣言的に登録されている場合、通常はアノテーションを介してのみ設定できます。対照的に、拡張機能が @RegisterExtension を介して登録されている場合、拡張機能のコンストラクタ、静的ファクトリメソッド、またはビルダー API に引数を渡すなど、プログラムで設定できます。

拡張機能の登録順序

デフォルトでは、@RegisterExtension を介してプログラムで、またはフィールドの @ExtendWith を介して宣言的に登録された拡張機能は、決定論的ではあるが意図的にわかりにくいアルゴリズムを使用して順序付けられます。これにより、テストスイートの後続の実行で拡張機能が同じ順序で実行され、反復可能なビルドが可能になります。ただし、拡張機能を明示的な順序で登録する必要がある場合があります。これを実現するには、@RegisterExtension フィールドまたは @ExtendWith フィールドに @Order でアノテーションを付けます。

@RegisterExtensionフィールドまたは@ExtendWithフィールドで、@Orderアノテーションが付いていないものは、Integer.MAX_VALUE / 2の値を持つデフォルトの順序で並べられます。これにより、@Orderアノテーションが付いた拡張フィールドを、アノテーションが付いていない拡張フィールドの前または後に明示的に順序付けできます。明示的な順序値がデフォルトの順序値よりも小さい拡張機能は、アノテーションが付いていない拡張機能よりも前に登録されます。同様に、明示的な順序値がデフォルトの順序値よりも大きい拡張機能は、アノテーションが付いていない拡張機能よりも後に登録されます。たとえば、拡張機能にデフォルトの順序値よりも大きい明示的な順序値を割り当てると、他のプログラムで登録された拡張機能に対して、beforeコールバック拡張機能を最後に登録し、afterコールバック拡張機能を最初に登録できます。

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

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

次の例では、テストクラスのserverフィールドは、WebServerExtensionでサポートされているビルダーパターンを使用してプログラムで初期化されます。構成されたWebServerExtensionは、クラスレベルで拡張機能として自動的に登録されます。たとえば、クラス内のすべてのテストの前にサーバーを起動し、クラス内のすべてのテストが完了した後にサーバーを停止するためです。さらに、@BeforeAllまたは@AfterAllでアノテーションが付けられた静的ライフサイクルメソッド、および@BeforeEach@AfterEach、および@Testメソッドは、必要に応じてserverフィールドを介して拡張機能のインスタンスにアクセスできます。

Javaの静的フィールドを介した拡張機能の登録
class WebServerDemo {

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

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

}
Kotlinの静的フィールド

Kotlinプログラミング言語には、staticフィールドの概念はありません。ただし、コンパイラーに、Kotlinの@JvmStaticアノテーションを使用してprivate staticフィールドを生成するように指示できます。Kotlinコンパイラーにpublic staticフィールドを生成させたい場合は、代わりに@JvmFieldアノテーションを使用できます。

次の例は、前のセクションのWebServerDemoをKotlinに移植したものです。

Kotlinの静的フィールドを介した拡張機能の登録
class KotlinWebServerDemo {

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

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

@RegisterExtensionフィールドが非static(つまり、インスタンスフィールド)の場合、拡張機能は、テストクラスがインスタンス化された後、および登録済みの各TestInstancePostProcessorがテストインスタンスを後処理する機会を与えられた後(アノテーション付きフィールドで使用する拡張機能のインスタンスを注入する可能性があります)に登録されます。したがって、そのようなインスタンス拡張機能BeforeAllCallbackAfterAllCallback、またはTestInstancePostProcessorなどのクラスレベルまたはインスタンスレベルの拡張APIを実装している場合、これらのAPIは尊重されません。デフォルトでは、インスタンス拡張機能は、@ExtendWithを介してメソッドレベルで登録された拡張機能のに登録されます。ただし、テストクラスが@TestInstance(Lifecycle.PER_CLASS)セマンティクスで構成されている場合、インスタンス拡張機能は、@ExtendWithを介してメソッドレベルで登録された拡張機能のに登録されます。

次の例では、テストクラスのdocsフィールドは、カスタムのlookUpDocsDir()メソッドを呼び出し、その結果をDocumentationExtensionの静的forPath()ファクトリメソッドに渡すことによって、プログラムで初期化されます。構成されたDocumentationExtensionは、メソッドレベルで拡張機能として自動的に登録されます。さらに、@BeforeEach@AfterEach、および@Testメソッドは、必要に応じてdocsフィールドを介して拡張機能のインスタンスにアクセスできます。

インスタンスフィールドを介して登録された拡張機能
class DocumentationDemo {

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

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

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

5.2.3. 自動拡張機能登録

宣言的な拡張機能登録およびプログラムによる拡張機能登録のサポートに加えて、JUnit Jupiterは、JavaのServiceLoaderメカニズムを介したグローバル拡張機能登録もサポートしており、クラスパスで使用可能なものに基づいて、サードパーティの拡張機能を自動検出して自動的に登録できるようにします。

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

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

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

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

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

自動検出が有効になっている場合、ServiceLoaderメカニズムを介して検出された拡張機能は、JUnit Jupiterのグローバル拡張機能(TestInfoTestReporterなどのサポート)の後に拡張機能レジストリに追加されます。

5.2.4. 拡張機能の継承

登録された拡張機能は、トップダウンセマンティクスでテストクラス階層内で継承されます。同様に、クラスレベルで登録された拡張機能は、メソッドレベルで継承されます。さらに、特定の拡張機能の実装は、特定の拡張機能コンテキストとその親コンテキストに対して1回のみ登録できます。したがって、重複した拡張機能の実装を登録しようとすると、無視されます。

5.3. 条件付きテスト実行

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

ExecutionConditionは、提供されたExtensionContextに基づいて、その中に含まれるすべてのテストを実行する必要があるかどうかを判断するために、各コンテナ(例:テストクラス)に対して評価されます。同様に、ExecutionConditionは、提供されたExtensionContextに基づいて、特定のテストメソッドを実行する必要があるかどうかを判断するために、各テストに対して評価されます。

複数のExecutionCondition拡張機能が登録されている場合、条件の1つが無効を返すとすぐに、コンテナまたはテストは無効になります。したがって、別の拡張機能がコンテナまたはテストをすでに無効にしている可能性があるため、条件が評価されるという保証はありません。言い換えれば、評価は短絡ブールOR演算子のように機能します。

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

5.3.1. 条件の非アクティブ化

特定の条件がアクティブでない状態でテストスイートを実行すると便利な場合があります。たとえば、まだ壊れているかどうかを確認するために、@Disabledでアノテーションが付けられている場合でも、テストを実行したい場合があります。これを行うには、junit.jupiter.conditions.deactivate構成パラメーターのパターンを指定して、現在のテスト実行で非アクティブ化(つまり、評価しない)する必要がある条件を指定します。このパターンは、JVMシステムプロパティ、Launcherに渡されるLauncherDiscoveryRequest構成パラメーターとして、またはJUnitプラットフォーム構成ファイル(詳細については構成パラメーターを参照)を介して指定できます。

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

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

パターンマッチング構文

詳細については、パターンマッチング構文を参照してください。

5.4. テストインスタンスの事前構築コールバック

TestInstancePreConstructCallbackは、テストインスタンスが(コンストラクター呼び出しまたはTestInstanceFactoryを介して)構築されるに呼び出されるExtensionsのAPIを定義します。

この拡張機能は、TestInstancePreDestroyCallbackへの対称的な呼び出しを提供し、コンストラクターパラメーターを準備したり、テストインスタンスとそのライフサイクルを追跡したりするために他の拡張機能と組み合わせて使用すると便利です。

5.5. テストインスタンスファクトリー

TestInstanceFactoryは、テストクラスインスタンスを作成したいExtensionsのAPIを定義します。

一般的なユースケースとしては、依存性注入フレームワークからテストインスタンスを取得したり、静的ファクトリメソッドを呼び出してテストクラスインスタンスを作成したりすることが含まれます。

TestInstanceFactoryが登録されていない場合、フレームワークはテストクラスの唯一のコンストラクターを呼び出してインスタンス化し、登録済みのParameterResolver拡張機能を介してコンストラクター引数を解決する可能性があります。

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

1つのクラスに対してTestInstanceFactoryを実装する複数の拡張機能を登録すると、そのクラス、任意のサブクラス、および任意のネストされたクラスのすべてのテストで例外がスローされます。スーパークラスまたは囲みクラス(つまり、@Nestedテストクラスの場合)に登録されたTestInstanceFactory継承されることに注意してください。特定のテストクラスに対して単一のTestInstanceFactoryのみが登録されていることを保証するのは、ユーザーの責任です。

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

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

一般的なユースケースとしては、テストインスタンスへの依存性の注入、テストインスタンス上でのカスタム初期化メソッドの呼び出しなどが挙げられます。

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

5.7. テストインスタンスの破棄前コールバック

TestInstancePreDestroyCallback は、テストで使用された、破棄されるにテストインスタンスを処理したいExtensionsのためのAPIを定義します。

一般的なユースケースとしては、テストインスタンスに注入された依存関係のクリーンアップ、テストインスタンス上でのカスタム終了処理メソッドの呼び出しなどが挙げられます。

5.8. パラメータ解決

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

テストクラスのコンストラクタ、テストメソッド、またはライフサイクルメソッド定義を参照)がパラメータを宣言する場合、パラメータはParameterResolverによって実行時に解決される必要があります。ParameterResolver は、組み込みのもの(TestInfoParameterResolver を参照)であるか、ユーザーによって登録されたものである必要があります。一般的に、パラメータは名前アノテーション、またはそれらの組み合わせによって解決できます。

パラメータの型のみに基づいてパラメータを解決するカスタムの ParameterResolver を実装したい場合、そのようなユースケースのための汎用アダプタとして機能する TypeBasedParameterResolver を拡張すると便利です。

具体的な例については、CustomTypeParameterResolverCustomAnnotationParameterResolver、および MapOfListsTypeBasedParameterResolver のソースコードを参照してください。

JDK 9 より前の JDK バージョンで javac によって生成されたバイトコードのバグにより、コア java.lang.reflect.Parameter API を介してパラメータのアノテーションを直接検索すると、インナークラスコンストラクタ(例えば、@Nested テストクラスのコンストラクタ)の場合、常に失敗します。

したがって、ParameterResolver 実装に提供される ParameterContext API には、パラメータのアノテーションを正しく検索するための次の便利なメソッドが含まれています。Extension の作成者は、JDK のこのバグを回避するために、java.lang.reflect.Parameter で提供されるメソッドの代わりにこれらのメソッドを使用することを強く推奨します。

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

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

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

他の拡張機能は、ExtensionContextgetExecutableInvoker() メソッドを介して利用可能な ExecutableInvoker を使用して、メソッドとコンストラクタの呼び出しに登録済みの ParameterResolver を利用することもできます。

5.9. テスト結果処理

TestWatcher は、テストメソッドの実行結果を処理したい拡張機能のための API を定義します。具体的には、TestWatcher は以下のイベントに関するコンテキスト情報とともに呼び出されます。

  • testDisabled:無効化されたテストメソッドがスキップされた後に呼び出されます

  • testSuccessfulテストメソッドが正常に完了した後に呼び出されます

  • testAbortedテストメソッドが中断された後に呼び出されます

  • testFailedテストメソッドが失敗した後に呼び出されます

定義で提示されている「テストメソッド」の定義とは対照的に、このコンテキストでは、テストメソッドは任意の @Test メソッドまたは @TestTemplate メソッド(例えば、@RepeatedTest または @ParameterizedTest)を指します。

このインターフェースを実装する拡張機能は、クラスレベル、インスタンスレベル、またはメソッドレベルで登録できます。クラスレベルで登録された場合、TestWatcher は、@Nested クラス内のものを含む、含まれているすべてのテストメソッドに対して呼び出されます。メソッドレベルで登録された場合、TestWatcher は、登録されたテストメソッドに対してのみ呼び出されます。

@RegisterExtension を使用するなど、非静的(インスタンス)フィールドを介して TestWatcher が登録され、テストクラスが @TestInstance(Lifecycle.PER_METHOD) セマンティクス(これがデフォルトのライフサイクルモード)で構成されている場合、TestWatcher@TestTemplate メソッド(例えば、@RepeatedTest または @ParameterizedTest)のイベントで呼び出されません

したがって、TestWatcher が特定のクラスのすべてのテストメソッドに対して呼び出されるようにするには、TestWatcher をクラスレベルで @ExtendWith で登録するか、@RegisterExtension または @ExtendWith を使用して static フィールドで登録することをお勧めします。

クラスレベルで失敗があった場合(例えば、@BeforeAll メソッドによって例外がスローされた場合)、テスト結果は報告されません。同様に、テストクラスが ExecutionCondition(例えば、@Disabled)を介して無効化された場合、テスト結果は報告されません。

他の拡張 API とは対照的に、TestWatcher はテストの実行に悪影響を与えることは許可されていません。したがって、TestWatcher API のメソッドによってスローされた例外は WARNING レベルでログに記録され、伝播またはテスト実行の失敗は許可されません。

提供された ExtensionContextStore に格納されている ExtensionContext.Store.CloseableResource のインスタンスは、TestWatcher API のメソッドが呼び出されるにクローズされます(拡張機能の状態の保持を参照)。親コンテキストの Store を使用して、そのようなリソースを操作できます。

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

以下のインターフェースは、テスト実行ライフサイクルのさまざまな時点でテストを拡張するための API を定義します。例については以下のセクションを参照し、詳細については org.junit.jupiter.api.extension パッケージ内のこれらの各インターフェースの Javadoc を参照してください。

複数の拡張APIの実装
拡張機能の開発者は、単一の拡張機能内でこれらのインターフェースを任意に実装できます。具体的な例については、SpringExtension のソースコードを参照してください。

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

BeforeTestExecutionCallbackAfterTestExecutionCallback は、テストメソッドが実行される直前直後に実行される動作を追加したい Extensions のための 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.11. 例外処理

テスト実行中にスローされた例外は、さらに伝播する前に、それに応じてインターセプトおよび処理される可能性があるため、エラーログやリソース解放などの特定のアクションを特殊な Extensions で定義できます。JUnit Jupiter は、@Test メソッド中にスローされた例外を TestExecutionExceptionHandler を介して処理したい Extensions 用の API を提供し、テストライフサイクルメソッド(@BeforeAll@BeforeEach@AfterEach、および @AfterAll)のいずれかでスローされた例外を LifecycleMethodExecutionExceptionHandler を介して処理したい Extensions 用の API を提供します。

次の例は、IOException のすべてのインスタンスを飲み込み、他の種類の例外は再スローする拡張機能を示しています。

テスト実行で IOExceptions をフィルタリングする例外処理拡張機能
public class IgnoreIOExceptionExtension implements TestExecutionExceptionHandler {

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

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

別の例は、設定とクリーンアップ中に予期しない例外がスローされたまさにその時点で、テスト対象のアプリケーションの状態を記録する方法を示しています。テストのステータスに応じて実行される場合とされない場合があるライフサイクルコールバックに依存するのとは異なり、このソリューションは、失敗した @BeforeAll@BeforeEach@AfterEach、または @AfterAll の直後に実行されることを保証します。

エラー時にアプリケーションの状態を記録する例外処理拡張機能
class RecordStateOnErrorExtension implements LifecycleMethodExecutionExceptionHandler {

    @Override
    public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex)
            throws Throwable {
        memoryDumpForFurtherInvestigation("Failure recorded during class setup");
        throw ex;
    }

    @Override
    public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex)
            throws Throwable {
        memoryDumpForFurtherInvestigation("Failure recorded during test setup");
        throw ex;
    }

    @Override
    public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable ex)
            throws Throwable {
        memoryDumpForFurtherInvestigation("Failure recorded during test cleanup");
        throw ex;
    }

    @Override
    public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable ex)
            throws Throwable {
        memoryDumpForFurtherInvestigation("Failure recorded during class cleanup");
        throw ex;
    }
}

複数の実行例外ハンドラーは、宣言順に同じライフサイクルメソッドに対して呼び出される場合があります。ハンドラーのいずれかが処理済みの例外を握りつぶした場合、後続のハンドラーは実行されず、例外がスローされなかったかのように、JUnitエンジンに失敗が伝播されることはありません。ハンドラーは、例外を再スローしたり、元の例外をラップするなど、別の例外をスローすることもできます。

LifecycleMethodExecutionExceptionHandler を実装する拡張機能で、@BeforeAll または @AfterAll の実行中にスローされた例外を処理したい場合は、クラスレベルで登録する必要があります。一方、BeforeEach および AfterEach のハンドラーは個々のテストメソッドに登録することもできます。

複数の例外処理拡張機能の登録
// Register handlers for @Test, @BeforeEach, @AfterEach as well as @BeforeAll and @AfterAll
@ExtendWith(ThirdExecutedHandler.class)
class MultipleHandlersTestCase {

    // Register handlers for @Test, @BeforeEach, @AfterEach only
    @ExtendWith(SecondExecutedHandler.class)
    @ExtendWith(FirstExecutedHandler.class)
    @Test
    void testMethod() {
    }

}

5.12. 呼び出しのインターセプト

InvocationInterceptor は、テストコードへの呼び出しをインターセプトしたい Extensions 用の API を定義します。

次の例は、Swingのイベントディスパッチスレッドで全てのテストメソッドを実行する拡張機能を示しています。

ユーザー定義のスレッドでテストを実行する拡張機能
public class SwingEdtInterceptor implements InvocationInterceptor {

    @Override
    public void interceptTestMethod(Invocation<Void> invocation,
            ReflectiveInvocationContext<Method> invocationContext,
            ExtensionContext extensionContext) throws Throwable {

        AtomicReference<Throwable> throwable = new AtomicReference<>();

        SwingUtilities.invokeAndWait(() -> {
            try {
                invocation.proceed();
            }
            catch (Throwable t) {
                throwable.set(t);
            }
        });
        Throwable t = throwable.get();
        if (t != null) {
            throw t;
        }
    }
}

5.13. テストテンプレートの呼び出しコンテキストの提供

@TestTemplate メソッドは、少なくとも1つの TestTemplateInvocationContextProvider が登録されている場合にのみ実行できます。それぞれのプロバイダーは、TestTemplateInvocationContext インスタンスの Stream を提供する役割を担います。それぞれのコンテキストは、カスタム表示名と、@TestTemplate メソッドの次の呼び出しでのみ使用される追加の拡張機能のリストを指定できます。

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

付随する拡張機能を使用したテストテンプレート
final List<String> fruits = Arrays.asList("apple", "banana", "lemon");

@TestTemplate
@ExtendWith(MyTestTemplateInvocationContextProvider.class)
void testTemplate(String fruit) {
    assertTrue(fruits.contains(fruit));
}

public class MyTestTemplateInvocationContextProvider
        implements TestTemplateInvocationContextProvider {

    @Override
    public boolean supportsTestTemplate(ExtensionContext context) {
        return true;
    }

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
            ExtensionContext context) {

        return Stream.of(invocationContext("apple"), invocationContext("banana"));
    }

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

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

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

この例では、テストテンプレートは2回呼び出されます。呼び出しの表示名は、呼び出しコンテキストで指定されたとおりに、apple および banana になります。各呼び出しは、メソッドパラメータを解決するために使用されるカスタム ParameterResolver を登録します。ConsoleLauncher を使用した場合の出力は次のとおりです。

└─ testTemplate(String) ✔
   ├─ apple ✔
   └─ banana ✔

TestTemplateInvocationContextProvider 拡張APIは、テストのようなメソッドの反復的な呼び出しに依存するさまざまな種類のテストを実装することを主な目的としています。たとえば、異なるパラメータを使用したり、テストクラスのインスタンスを異なる方法で準備したり、コンテキストを変更せずに複数回実行したりする場合などです。この拡張ポイントを使用して機能を提供するリピートテストまたはパラメータ化テストの実装を参照してください。

5.14. 拡張機能の状態の保持

通常、拡張機能は一度だけインスタンス化されます。したがって、次の疑問が重要になります。拡張機能の1回の呼び出しから次の呼び出しまで状態をどのように保持するのですか?ExtensionContext APIは、この目的のために正確にStoreを提供します。拡張機能は、後で取得できるように、値をストアに入れることができます。メソッドレベルのスコープでStoreを使用する例については、TimingExtensionを参照してください。テスト実行中にExtensionContextに保存された値は、周囲のExtensionContextでは利用できないことに注意することが重要です。ExtensionContextsはネストされる可能性があるため、内部コンテキストのスコープも制限される可能性があります。Storeを介して値を保存および取得するために利用できるメソッドの詳細については、対応するJavadocを参照してください。

ExtensionContext.Store.CloseableResource
拡張コンテキストストアは、その拡張コンテキストのライフサイクルにバインドされています。拡張コンテキストのライフサイクルが終了すると、関連付けられたストアが閉じられます。CloseableResourceのインスタンスであるすべての保存された値は、追加された順序の逆順で close() メソッドの呼び出しによって通知されます。

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

junit-platform-commonsアーティファクトは、org.junit.platform.commons.supportという名前のパッケージを公開しており、アノテーション、クラス、リフレクション、クラスパススキャンタスクを操作するための*維持された*ユーティリティメソッドが含まれています。TestEngineおよびExtensionの作成者は、JUnitプラットフォームの動作と一致させるために、これらのサポートされているメソッドを使用することをお勧めします。

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

AnnotationSupport は、アノテーション付きの要素(例:パッケージ、アノテーション、クラス、インターフェース、コンストラクター、メソッド、およびフィールド)で動作する静的ユーティリティメソッドを提供します。これらには、特定の注釈で要素に注釈が付いているか、メタ注釈が付いているかを確認するメソッド、特定の注釈を検索するメソッド、クラスまたはインターフェース内の注釈付きメソッドとフィールドを見つけるメソッドが含まれます。これらのメソッドの一部は、実装されたインターフェースとクラス階層を検索して、アノテーションを見つけます。詳細については、AnnotationSupport のJavadocを参照してください。

5.15.2. クラスのサポート

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

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

ReflectionSupport は、標準のJDKリフレクションおよびクラスローディングメカニズムを拡張する静的ユーティリティメソッドを提供します。これらには、指定された述語に一致するクラスを検索するためにクラスパスをスキャンするメソッド、クラスの新しいインスタンスをロードおよび作成するメソッド、メソッドを検索および呼び出すメソッドが含まれます。これらのメソッドの一部は、クラス階層をトラバースして、一致するメソッドを検索します。詳細については、ReflectionSupport のJavadocを参照してください。

5.15.4. 修飾子のサポート

ModifierSupport は、メンバーおよびクラスの修飾子を操作するための静的ユーティリティメソッドを提供します。たとえば、メンバーが publicprivateabstractstatic などとして宣言されているかどうかを判断します。詳細については、ModifierSupport のJavadocを参照してください。

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

1つ以上のテストメソッドを含むテストクラスを実行すると、ユーザーが提供するテストおよびライフサイクルメソッドに加えて、多くの拡張機能コールバックが呼び出されます。

以下も参照してください: テストの実行順序

5.16.1. ユーザーコードと拡張機能コード

次の図は、ユーザーが提供するコードと拡張機能コードの相対的な順序を示しています。ユーザーが提供するテストおよびライフサイクルメソッドはオレンジ色で表示され、拡張機能によって実装されたコールバックコードは青色で表示されます。灰色のボックスは単一のテストメソッドの実行を示しており、テストクラスのすべてのテストメソッドに対して繰り返されます。

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

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

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

1

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

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

2

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

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

3

インターフェース org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleBeforeAllMethodExecutionException

@BeforeAll メソッドからスローされた例外を処理するための拡張機能コード

4

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

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

5

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

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

6

インターフェース org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleBeforeEachMethodExecutionException

@BeforeEach メソッドからスローされた例外を処理するための拡張機能コード

7

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

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

8

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

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

9

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

テスト中にスローされた例外を処理するための拡張機能コード

10

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

テスト実行とその対応する例外ハンドラーの直後に実行される拡張機能コード

11

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

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

12

インターフェース org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleAfterEachMethodExecutionException

@AfterEach メソッドからスローされた例外を処理するための拡張機能コード

13

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

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

14

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

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

15

インターフェース org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleAfterAllMethodExecutionException

@AfterAll メソッドからスローされた例外を処理するための拡張機能コード

16

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

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

最も単純なケースでは、実際のテストメソッドのみが実行されます(ステップ8)。他のすべてのステップは、対応するライフサイクルコールバックのユーザーコードまたは拡張機能サポートの存在に応じてオプションです。さまざまなライフサイクルコールバックの詳細については、各アノテーションと拡張機能の対応するJavadocを参照してください。

上記の表のユーザーコードメソッドのすべての呼び出しは、InvocationInterceptorを実装することで追加的にインターセプトできます。

5.16.2. コールバックのラッピング動作

JUnit Jupiterは、BeforeAllCallbackAfterAllCallbackBeforeEachCallbackAfterEachCallbackBeforeTestExecutionCallback、およびAfterTestExecutionCallbackなどのライフサイクルコールバックを実装する複数の登録済み拡張機能に対して、常にラッピング動作を保証します。

つまり、Extension1Extension2の前に登録されている2つの拡張機能 Extension1Extension2 がある場合、Extension1によって実装された「before」コールバックは、Extension2によって実装された「before」コールバックの前に実行されることが保証されます。同様に、同じ順序で登録された同じ2つの拡張機能がある場合、Extension1によって実装された「after」コールバックは、Extension2によって実装された「after」コールバックのに実行されることが保証されます。したがって、Extension1Extension2ラップすると言えます。

JUnit Jupiter は、ユーザーが提供するライフサイクルメソッド定義を参照)に対して、クラスおよびインターフェース階層内でのラッピング動作を保証します。

  • @BeforeAll メソッドは、隠蔽オーバーライド、または代替(Java の可視性規則に関係なく、シグネチャのみに基づいて置き換えられること)されていない限り、スーパークラスから継承されます。さらに、スーパークラスの @BeforeAll メソッドは、サブクラスの @BeforeAll メソッドより前に実行されます。

    • 同様に、インターフェースで宣言された @BeforeAll メソッドは、隠蔽またはオーバーライドされていない限り継承され、インターフェースの @BeforeAll メソッドは、そのインターフェースを実装するクラスの @BeforeAll メソッドより前に実行されます。

  • @AfterAll メソッドは、隠蔽オーバーライド、または代替(Java の可視性規則に関係なく、シグネチャのみに基づいて置き換えられること)されていない限り、スーパークラスから継承されます。さらに、スーパークラスの @AfterAll メソッドは、サブクラスの @AfterAll メソッドより後に実行されます。

    • 同様に、インターフェースで宣言された @AfterAll メソッドは、隠蔽またはオーバーライドされていない限り継承され、インターフェースの @AfterAll メソッドは、そのインターフェースを実装するクラスの @AfterAll メソッドより後に実行されます。

  • @BeforeEach メソッドは、オーバーライドまたは代替(Java の可視性規則に関係なく、シグネチャのみに基づいて置き換えられること)されていない限り、スーパークラスから継承されます。さらに、スーパークラスの @BeforeEach メソッドは、サブクラスの @BeforeEach メソッドより前に実行されます。

    • 同様に、インターフェースのデフォルトメソッドとして宣言された @BeforeEach メソッドは、オーバーライドされていない限り継承され、インターフェースのデフォルトの @BeforeEach メソッドは、そのインターフェースを実装するクラスの @BeforeEach メソッドより前に実行されます。

  • @AfterEach メソッドは、オーバーライドまたは代替(Java の可視性規則に関係なく、シグネチャのみに基づいて置き換えられること)されていない限り、スーパークラスから継承されます。さらに、スーパークラスの @AfterEach メソッドは、サブクラスの @AfterEach メソッドより後に実行されます。

    • 同様に、インターフェースのデフォルトメソッドとして宣言された @AfterEach メソッドは、オーバーライドされていない限り継承され、インターフェースのデフォルトの @AfterEach メソッドは、そのインターフェースを実装するクラスの @AfterEach メソッドより後に実行されます。

以下の例は、この動作を示しています。これらの例は実際には現実的なことは何も行わないことに注意してください。代わりに、データベースとの相互作用をテストするための一般的なシナリオを模倣しています。Logger クラスから静的にインポートされたすべてのメソッドは、ユーザーが提供するコールバックメソッドと拡張機能のコールバックメソッドの実行順序を理解するのに役立つコンテキスト情報をログに記録します。

Extension1
import static example.callbacks.Logger.afterEachCallback;
import static example.callbacks.Logger.beforeEachCallback;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class Extension1 implements BeforeEachCallback, AfterEachCallback {

    @Override
    public void beforeEach(ExtensionContext context) {
        beforeEachCallback(this);
    }

    @Override
    public void afterEach(ExtensionContext context) {
        afterEachCallback(this);
    }

}
Extension2
import static example.callbacks.Logger.afterEachCallback;
import static example.callbacks.Logger.beforeEachCallback;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class Extension2 implements BeforeEachCallback, AfterEachCallback {

    @Override
    public void beforeEach(ExtensionContext context) {
        beforeEachCallback(this);
    }

    @Override
    public void afterEach(ExtensionContext context) {
        afterEachCallback(this);
    }

}
AbstractDatabaseTests
import static example.callbacks.Logger.afterAllMethod;
import static example.callbacks.Logger.afterEachMethod;
import static example.callbacks.Logger.beforeAllMethod;
import static example.callbacks.Logger.beforeEachMethod;

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

/**
 * Abstract base class for tests that use the database.
 */
abstract class AbstractDatabaseTests {

    @BeforeAll
    static void createDatabase() {
        beforeAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".createDatabase()");
    }

    @BeforeEach
    void connectToDatabase() {
        beforeEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".connectToDatabase()");
    }

    @AfterEach
    void disconnectFromDatabase() {
        afterEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".disconnectFromDatabase()");
    }

    @AfterAll
    static void destroyDatabase() {
        afterAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".destroyDatabase()");
    }

}
DatabaseTestsDemo
import static example.callbacks.Logger.afterEachMethod;
import static example.callbacks.Logger.beforeAllMethod;
import static example.callbacks.Logger.beforeEachMethod;
import static example.callbacks.Logger.testMethod;

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

/**
 * Extension of {@link AbstractDatabaseTests} that inserts test data
 * into the database (after the database connection has been opened)
 * and deletes test data (before the database connection is closed).
 */
@ExtendWith({ Extension1.class, Extension2.class })
class DatabaseTestsDemo extends AbstractDatabaseTests {

    @BeforeAll
    static void beforeAll() {
        beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".beforeAll()");
    }

    @BeforeEach
    void insertTestDataIntoDatabase() {
        beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()");
    }

    @Test
    void testDatabaseFunctionality() {
        testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()");
    }

    @AfterEach
    void deleteTestDataFromDatabase() {
        afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()");
    }

    @AfterAll
    static void afterAll() {
        beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".afterAll()");
    }

}

DatabaseTestsDemo テストクラスが実行されると、以下がログに記録されます。

@BeforeAll AbstractDatabaseTests.createDatabase()
@BeforeAll DatabaseTestsDemo.beforeAll()
  Extension1.beforeEach()
  Extension2.beforeEach()
    @BeforeEach AbstractDatabaseTests.connectToDatabase()
    @BeforeEach DatabaseTestsDemo.insertTestDataIntoDatabase()
      @Test DatabaseTestsDemo.testDatabaseFunctionality()
    @AfterEach DatabaseTestsDemo.deleteTestDataFromDatabase()
    @AfterEach AbstractDatabaseTests.disconnectFromDatabase()
  Extension2.afterEach()
  Extension1.afterEach()
@BeforeAll DatabaseTestsDemo.afterAll()
@AfterAll AbstractDatabaseTests.destroyDatabase()

次のシーケンス図は、DatabaseTestsDemo テストクラスが実行されたときに JupiterTestEngine 内で実際に何が起こるかをさらに詳しく説明するのに役立ちます。

extensions DatabaseTestsDemo
DatabaseTestsDemo

JUnit Jupiter は、単一のテストクラスまたはテストインターフェース内で宣言された複数のライフサイクルメソッドの実行順序を保証しません。JUnit Jupiter が、そのようなメソッドをアルファベット順に呼び出すように見える場合があります。ただし、それは厳密には真実ではありません。順序付けは、単一のテストクラス内の @Test メソッドの順序付けに類似しています。

単一のテストクラスまたはテストインターフェース内で宣言されたライフサイクルメソッドは、決定論的でありながら意図的にわかりにくいアルゴリズムを使用して順序付けられます。これにより、テストスイートの後続の実行でライフサイクルメソッドが同じ順序で実行されることが保証され、再現可能なビルドが可能になります。

さらに、JUnit Jupiter は、単一のテストクラスまたはテストインターフェース内で宣言された複数のライフサイクルメソッドのラッピング動作をサポートしません。

次の例は、この動作を示しています。具体的には、ローカルで宣言されたライフサイクルメソッドが実行される順序のために、ライフサイクルメソッドの構成が壊れています。

  • データベース接続が開かれるにテストデータが挿入されるため、データベースへの接続に失敗します。

  • テストデータが削除されるにデータベース接続が閉じられるため、データベースへの接続に失敗します。

BrokenLifecycleMethodConfigDemo
import static example.callbacks.Logger.afterEachMethod;
import static example.callbacks.Logger.beforeEachMethod;
import static example.callbacks.Logger.testMethod;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

/**
 * Example of "broken" lifecycle method configuration.
 *
 * <p>Test data is inserted before the database connection has been opened.
 *
 * <p>Database connection is closed before deleting test data.
 */
@ExtendWith({ Extension1.class, Extension2.class })
class BrokenLifecycleMethodConfigDemo {

    @BeforeEach
    void connectToDatabase() {
        beforeEachMethod(getClass().getSimpleName() + ".connectToDatabase()");
    }

    @BeforeEach
    void insertTestDataIntoDatabase() {
        beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()");
    }

    @Test
    void testDatabaseFunctionality() {
        testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()");
    }

    @AfterEach
    void deleteTestDataFromDatabase() {
        afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()");
    }

    @AfterEach
    void disconnectFromDatabase() {
        afterEachMethod(getClass().getSimpleName() + ".disconnectFromDatabase()");
    }

}

BrokenLifecycleMethodConfigDemo テストクラスが実行されると、以下がログに記録されます。

Extension1.beforeEach()
Extension2.beforeEach()
  @BeforeEach BrokenLifecycleMethodConfigDemo.insertTestDataIntoDatabase()
  @BeforeEach BrokenLifecycleMethodConfigDemo.connectToDatabase()
    @Test BrokenLifecycleMethodConfigDemo.testDatabaseFunctionality()
  @AfterEach BrokenLifecycleMethodConfigDemo.disconnectFromDatabase()
  @AfterEach BrokenLifecycleMethodConfigDemo.deleteTestDataFromDatabase()
Extension2.afterEach()
Extension1.afterEach()

次のシーケンス図は、BrokenLifecycleMethodConfigDemo テストクラスが実行されたときに JupiterTestEngine 内で実際に何が起こるかをさらに詳しく説明するのに役立ちます。

extensions BrokenLifecycleMethodConfigDemo
BrokenLifecycleMethodConfigDemo

前述の動作により、JUnit Team は、そのようなライフサイクルメソッド間に依存関係がない場合を除き、開発者がテストクラスまたはテストインターフェースごとに各タイプのライフサイクルメソッド定義を参照)を最大で 1 つ宣言することを推奨しています。

6. 高度なトピック

6.1. JUnit Platform レポート

junit-platform-reporting アーティファクトには、XML テストレポートを 2 つの形式で生成する TestExecutionListener 実装が含まれています。1 つはレガシー形式、もう 1 つはOpen Test Reporting形式です。

このモジュールには、カスタムレポートを作成するために使用できる他の TestExecutionListener 実装も含まれています。詳細については、リスナーとインターセプターの使用を参照してください。

6.1.1. レガシー XML 形式

LegacyXmlReportGeneratingListener は、TestPlan 内のルートごとに個別の XML レポートを生成します。生成された XML 形式は、Ant ビルドシステムによって普及した JUnit 4 ベースのテストレポートの事実上の標準と互換性があることに注意してください。

LegacyXmlReportGeneratingListener は、コンソールランチャーでも使用されます。

6.1.2. Open Test Reporting XML 形式

OpenTestReportGeneratingListener は、Open Test Reporting で指定されたイベントベースの形式で、実行全体に対する XML レポートを書き込みます。これにより、階層的なテスト構造、表示名、タグなど、JUnit Platform のすべての機能がサポートされます。

リスナーは自動的に登録され、次の構成パラメーターで構成できます。

junit.platform.reporting.open.xml.enabled=true|false

レポートの書き込みを有効または無効にします。

junit.platform.reporting.output.dir=<path>

レポートの出力ディレクトリを構成します。デフォルトでは、Gradle ビルドスクリプトが見つかった場合は build、Maven POM が見つかった場合は target が使用されます。それ以外の場合は、現在の作業ディレクトリが使用されます。

有効にすると、リスナーは構成された出力ディレクトリに、テスト実行ごとに junit-platform-events-<ランダム ID>.xml という名前の XML レポートファイルを作成します。

Open Test Reporting CLI ツールを使用すると、イベントベースの形式から、より人間が読みやすい階層形式に変換できます。
Gradle

Gradle の場合、Open Test Reporting 互換の XML レポートの書き込みは、システムプロパティを介して有効および構成できます。次のサンプルでは、Gradle が独自の XML レポートに使用するディレクトリと同じディレクトリになるように出力ディレクトリを構成しています。CommandLineArgumentProvider は、Gradle のビルドキャッシュを使用する場合に重要な、異なるマシン間でタスクを再配置できるようにするために使用されます。

Groovy DSL
dependencies {
    testRuntimeOnly("org.junit.platform:junit-platform-reporting:1.10.2")
}
tasks.withType(Test).configureEach {
    def outputDir = reports.junitXml.outputLocation
    jvmArgumentProviders << ({
        [
            "-Djunit.platform.reporting.open.xml.enabled=true",
            "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}"
        ]
    } as CommandLineArgumentProvider)
}
Kotlin DSL
dependencies {
    testRuntimeOnly("org.junit.platform:junit-platform-reporting:1.10.2")
}
tasks.withType<Test>().configureEach {
    val outputDir = reports.junitXml.outputLocation
    jvmArgumentProviders += CommandLineArgumentProvider {
        listOf(
            "-Djunit.platform.reporting.open.xml.enabled=true",
            "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}"
        )
    }
}
Maven

Maven Surefire/Failsafe の場合、Open Test Reporting 出力を有効にし、結果の XML ファイルが Surefire/Failsafe が独自の XML レポートに使用するディレクトリと同じディレクトリに書き込まれるように構成できます。次に例を示します。

<project>
    <!-- ... -->
    <dependencies>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-reporting</artifactId>
            <version>1.10.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.1.2</version>
                <configuration>
                    <properties>
                        <configurationParameters>
                            junit.platform.reporting.open.xml.enabled = true
                            junit.platform.reporting.output.dir = target/surefire-reports
                        </configurationParameters>
                    </properties>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <!-- ... -->
</project>
コンソールランチャー

コンソールランチャーを使用する場合は、--config を使用して構成パラメーターを設定することにより、Open Test Reporting 出力を有効にできます。

$ java -jar junit-platform-console-standalone-1.10.2.jar <OPTIONS> \
  --config=junit.platform.reporting.open.xml.enabled=true \
  --config=junit.platform.reporting.output.dir=reports

6.2. JUnit Platform スイートエンジン

JUnit Platform は、JUnit Platform を使用してあらゆるテストエンジンからのテストスイートの宣言的な定義と実行をサポートします。

6.2.1. セットアップ

junit-platform-suite-api および junit-platform-suite-engine アーティファクトに加えて、少なくとも 1 つの他のテストエンジンとその依存関係をクラスパスに含める必要があります。グループ ID、アーティファクト ID、およびバージョンに関する詳細については、依存関係メタデータを参照してください。

必須の依存関係
  • テストスコープの junit-platform-suite-api: テストスイートを構成するために必要なアノテーションを含むアーティファクト

  • テストランタイムスコープの junit-platform-suite-engine: 宣言的なテストスイート用の TestEngine API の実装

必要な依存関係は両方とも junit-platform-suite アーティファクトに集約されており、junit-platform-suite-apijunit-platform-suite-engine に明示的な依存関係を宣言する代わりに、テストスコープで宣言できます。
推移的な依存関係
  • test スコープのjunit-platform-suite-commons

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

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

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

  • test スコープのopentest4j

6.2.2. @Suite の例

クラスに @Suite アノテーションを付けることで、JUnit Platform でテストスイートとしてマークされます。次の例に示すように、セレクターとフィルターアノテーションを使用して、スイートの内容を制御できます。

import org.junit.platform.suite.api.IncludeClassNamePatterns;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
import org.junit.platform.suite.api.SuiteDisplayName;

@Suite
@SuiteDisplayName("JUnit Platform Suite Demo")
@SelectPackages("example")
@IncludeClassNamePatterns(".*Tests")
class SuiteDemo {
}
追加の構成オプション
テストスイートでテストを発見およびフィルタリングするための多数の構成オプションがあります。サポートされているアノテーションの完全なリストと詳細については、org.junit.platform.suite.api パッケージの Javadoc を参照してください。

6.3. JUnit Platform テストキット

junit-platform-testkit アーティファクトは、JUnit Platform でテスト計画を実行し、期待される結果を検証するためのサポートを提供します。JUnit Platform 1.4 の時点では、このサポートは単一の TestEngine の実行に限定されています(エンジンテストキットを参照)。

6.3.1. エンジンテストキット

org.junit.platform.testkit.engine パッケージは、JUnit Platform上で実行される特定のTestEngineTestPlanを実行し、期待される結果を検証するために、fluent APIを介して結果にアクセスするサポートを提供します。このAPIへの主要なエントリポイントは、engine()およびexecute()という名前の静的ファクトリメソッドを提供するEngineTestKitです。LauncherDiscoveryRequestを構築するためのfluent APIを利用するために、engine()のバリアントのいずれかを選択することをお勧めします。

Launcher APIからLauncherDiscoveryRequestBuilderを使用してLauncherDiscoveryRequestを構築する場合は、EngineTestKitexecute()のバリアントのいずれかを使用する必要があります。

以下のJUnit Jupiterを使用して記述されたテストクラスは、後続の例で使用されます。

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

import example.util.Calculator;

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

@TestMethodOrder(OrderAnnotation.class)
public class ExampleTestCase {

    private final Calculator calculator = new Calculator();

    @Test
    @Disabled("for demonstration purposes")
    @Order(1)
    void skippedTest() {
        // skipped ...
    }

    @Test
    @Order(2)
    void succeedingTest() {
        assertEquals(42, calculator.multiply(6, 7));
    }

    @Test
    @Order(3)
    void abortedTest() {
        assumeTrue("abc".contains("Z"), "abc does not contain Z");
        // aborted ...
    }

    @Test
    @Order(4)
    void failingTest() {
        // The following throws an ArithmeticException: "/ by zero"
        calculator.divide(1, 0);
    }

}

簡潔にするために、以下のセクションでは、一意のエンジンIDが"junit-jupiter"であるJUnit自身のJupiterTestEngineをテストする方法を示します。独自のTestEngine実装をテストする場合は、独自の一意のエンジンIDを使用する必要があります。あるいは、EngineTestKit.engine(TestEngine)静的ファクトリメソッドにそのインスタンスを提供することにより、独自のTestEngineをテストできます。

6.3.2. 統計情報の検証

テストキットの最も一般的な機能の1つは、TestPlanの実行中に発生したイベントに対して統計情報を検証する機能です。次のテストは、JUnit JupiterのTestEngineにおけるコンテナおよびテストの統計情報を検証する方法を示しています。利用可能な統計情報の詳細については、EventStatisticsのJavadocを参照してください。

import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;

import example.ExampleTestCase;

import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;

class EngineTestKitStatisticsDemo {

    @Test
    void verifyJupiterContainerStats() {
        EngineTestKit
            .engine("junit-jupiter") (1)
            .selectors(selectClass(ExampleTestCase.class)) (2)
            .execute() (3)
            .containerEvents() (4)
            .assertStatistics(stats -> stats.started(2).succeeded(2)); (5)
    }

    @Test
    void verifyJupiterTestStats() {
        EngineTestKit
            .engine("junit-jupiter") (1)
            .selectors(selectClass(ExampleTestCase.class)) (2)
            .execute() (3)
            .testEvents() (6)
            .assertStatistics(stats ->
                stats.skipped(1).started(3).succeeded(1).aborted(1).failed(1)); (7)
    }

}
1 JUnit JupiterのTestEngineを選択します。
2 ExampleTestCaseテストクラスを選択します。
3 TestPlanを実行します。
4 コンテナイベントでフィルタリングします。
5 コンテナイベントの統計情報を検証します。
6 テストイベントでフィルタリングします。
7 テストイベントの統計情報を検証します。
verifyJupiterContainerStats()テストメソッドでは、JupiterTestEngineExampleTestCaseクラスの両方がコンテナと見なされるため、startedおよびsucceeded統計情報のカウントは2です。

6.3.3. イベントの検証

統計情報の検証だけではテスト実行の期待される動作を検証するのに不十分である場合は、記録されたEvent要素を直接操作し、それに対してアサーションを実行できます。

たとえば、ExampleTestCaseskippedTest()メソッドがスキップされた理由を検証する場合は、次の方法で実行できます。

次の例のassertThatEvents()メソッドは、AssertJアサーションライブラリのorg.assertj.core.api.Assertions.assertThat(events.list())のショートカットです。

イベントに対するAssertJアサーションで使用できる条件の詳細については、EventConditionsのJavadocを参照してください。

import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.test;

import example.ExampleTestCase;

import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.junit.platform.testkit.engine.Events;

class EngineTestKitSkippedMethodDemo {

    @Test
    void verifyJupiterMethodWasSkipped() {
        String methodName = "skippedTest";

        Events testEvents = EngineTestKit (5)
            .engine("junit-jupiter") (1)
            .selectors(selectMethod(ExampleTestCase.class, methodName)) (2)
            .execute() (3)
            .testEvents(); (4)

        testEvents.assertStatistics(stats -> stats.skipped(1)); (6)

        testEvents.assertThatEvents() (7)
            .haveExactly(1, event(test(methodName),
                skippedWithReason("for demonstration purposes")));
    }

}
1 JUnit JupiterのTestEngineを選択します。
2 ExampleTestCaseテストクラスのskippedTest()メソッドを選択します。
3 TestPlanを実行します。
4 テストイベントでフィルタリングします。
5 テストEventsをローカル変数に保存します。
6 オプションで、期待される統計情報を検証します。
7 記録されたテストイベントに、名前がskippedTestで、理由"for demonstration purposes"のスキップされたテストが1つだけ含まれていることを検証します。

ExampleTestCasefailingTest()メソッドからスローされた例外のタイプを検証する場合は、次の方法で実行できます。

イベントと実行結果に対するAssertJアサーションで使用できる条件の詳細については、EventConditionsおよびTestExecutionResultConditionsのJavadocをそれぞれ参照してください。

import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
import static org.junit.platform.testkit.engine.EventConditions.test;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;

import example.ExampleTestCase;

import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;

class EngineTestKitFailedMethodDemo {

    @Test
    void verifyJupiterMethodFailed() {
        EngineTestKit.engine("junit-jupiter") (1)
            .selectors(selectClass(ExampleTestCase.class)) (2)
            .execute() (3)
            .testEvents() (4)
            .assertThatEvents().haveExactly(1, (5)
                event(test("failingTest"),
                    finishedWithFailure(
                        instanceOf(ArithmeticException.class), message("/ by zero"))));
    }

}
1 JUnit JupiterのTestEngineを選択します。
2 ExampleTestCaseテストクラスを選択します。
3 TestPlanを実行します。
4 テストイベントでフィルタリングします。
5 記録されたテストイベントに、名前がfailingTestで、タイプがArithmeticExceptionの例外と、エラーメッセージが"/ by zero"の失敗したテストが1つだけ含まれていることを検証します。

通常は不要ですが、TestPlanの実行中に発生したすべてのイベントを検証する必要がある場合があります。次のテストは、EngineTestKit APIのassertEventsMatchExactly()メソッドを使用してこれを実現する方法を示しています。

assertEventsMatchExactly()は、イベントが発生した順序と正確に一致するため、ExampleTestCaseには@TestMethodOrder(OrderAnnotation.class)のアノテーションが付けられ、各テストメソッドには@Order(…​)のアノテーションが付けられています。これにより、テストメソッドが実行される順序を強制できます。これにより、verifyAllJupiterEvents()テストの信頼性を高めることができます。

順序付け要件の有無にかかわらず、部分的な一致を行う場合は、それぞれassertEventsMatchLooselyInOrder()メソッドとassertEventsMatchLoosely()メソッドを使用できます。

import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.container;
import static org.junit.platform.testkit.engine.EventConditions.engine;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully;
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.started;
import static org.junit.platform.testkit.engine.EventConditions.test;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;

import java.io.StringWriter;
import java.io.Writer;

import example.ExampleTestCase;

import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.opentest4j.TestAbortedException;

class EngineTestKitAllEventsDemo {

    @Test
    void verifyAllJupiterEvents() {
        Writer writer = // create a java.io.Writer for debug output

        EngineTestKit.engine("junit-jupiter") (1)
            .selectors(selectClass(ExampleTestCase.class)) (2)
            .execute() (3)
            .allEvents() (4)
            .debug(writer) (5)
            .assertEventsMatchExactly( (6)
                event(engine(), started()),
                event(container(ExampleTestCase.class), started()),
                event(test("skippedTest"), skippedWithReason("for demonstration purposes")),
                event(test("succeedingTest"), started()),
                event(test("succeedingTest"), finishedSuccessfully()),
                event(test("abortedTest"), started()),
                event(test("abortedTest"),
                    abortedWithReason(instanceOf(TestAbortedException.class),
                        message(m -> m.contains("abc does not contain Z")))),
                event(test("failingTest"), started()),
                event(test("failingTest"), finishedWithFailure(
                    instanceOf(ArithmeticException.class), message("/ by zero"))),
                event(container(ExampleTestCase.class), finishedSuccessfully()),
                event(engine(), finishedSuccessfully()));
    }

}
1 JUnit JupiterのTestEngineを選択します。
2 ExampleTestCaseテストクラスを選択します。
3 TestPlanを実行します。
4 すべてのイベントでフィルタリングします。
5 デバッグ目的で、指定されたwriterにすべてのイベントを出力します。デバッグ情報は、System.outSystem.errなどのOutputStreamに書き込むこともできます。
6 テストエンジンによって発生した順序と正確にすべてのイベントを検証します。

前の例のdebug()の呼び出しにより、次のような出力が得られます。

All Events:
    Event [type = STARTED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.082280Z, payload = null]
    Event [type = STARTED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.089339Z, payload = null]
    Event [type = SKIPPED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:skippedTest()], timestamp = 2018-12-14T12:45:14.094314Z, payload = 'for demonstration purposes']
    Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.095182Z, payload = null]
    Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.104922Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]]
    Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.106121Z, payload = null]
    Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.109956Z, payload = TestExecutionResult [status = ABORTED, throwable = org.opentest4j.TestAbortedException: Assumption failed: abc does not contain Z]]
    Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.110680Z, payload = null]
    Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.111217Z, payload = TestExecutionResult [status = FAILED, throwable = java.lang.ArithmeticException: / by zero]]
    Event [type = FINISHED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.113731Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]]
    Event [type = FINISHED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.113806Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]]

6.4. JUnit Platform Launcher API

JUnit 5の主要な目標の1つは、JUnitとそのプログラマティッククライアント(ビルドツールおよびIDE)間のインターフェースをより強力で安定したものにすることです。その目的は、テストの検出と実行の内部処理を、外部から必要なすべてのフィルタリングと構成から分離することです。

JUnit 5では、テストの検出、フィルタリング、実行に使用できるLauncherの概念が導入されています。さらに、Spock、Cucumber、FitNesseなどのサードパーティのテストライブラリは、カスタムTestEngineを提供することにより、JUnit Platformの起動インフラストラクチャにプラグインできます。

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

launcher APIの例として、ConsoleLauncherjunit-platform-consoleプロジェクトにあります。

6.4.1. テストの検出

プラットフォーム自体の専用機能としてテストの検出を持つことで、IDEとビルドツールは、以前のバージョンのJUnitでテストクラスとテストメソッドを識別するために経験する必要があったほとんどの困難から解放されます。

使用例

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

import java.io.PrintWriter;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryListener;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.LauncherSessionListener;
import org.junit.platform.launcher.PostDiscoveryFilter;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;
import org.junit.platform.launcher.core.LauncherConfig;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import org.junit.platform.launcher.listeners.TestExecutionSummary;
import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener;
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
    .selectors(
        selectPackage("com.example.mytests"),
        selectClass(MyTestClass.class)
    )
    .filters(
        includeClassNamePatterns(".*Tests")
    )
    .build();

try (LauncherSession session = LauncherFactory.openSession()) {
    TestPlan testPlan = session.getLauncher().discover(request);

    // ... discover additional test plans or execute tests
}

クラス、メソッド、およびパッケージ内のすべてのクラスを選択したり、クラスパスまたはモジュールパス内のすべてのテストを検索したりできます。検出は、参加しているすべてのテストエンジンにわたって行われます。

結果のTestPlanは、LauncherDiscoveryRequestに適合するすべてのエンジン、クラス、およびテストメソッドの階層(および読み取り専用)記述です。クライアントはツリーをトラバースし、ノードに関する詳細を取得し、元のソース(クラス、メソッド、ファイル位置など)へのリンクを取得できます。テストプランのすべてのノードには、特定のテストまたはテストグループを呼び出すために使用できる一意のIDがあります。

クライアントは、LauncherDiscoveryRequestBuilderを介して1つ以上のLauncherDiscoveryListener実装を登録して、テスト検出中に発生するイベントに関する洞察を得ることができます。デフォルトでは、ビルダは、最初の検出失敗が発生した後にテスト検出を中止する「失敗時に中止」リスナーを登録します。デフォルトのLauncherDiscoveryListenerは、junit.platform.discovery.listener.default構成パラメータを使用して変更できます。

6.4.2. テストの実行

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

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

SummaryGeneratingListener listener = new SummaryGeneratingListener();

try (LauncherSession session = LauncherFactory.openSession()) {
    Launcher launcher = session.getLauncher();
    // Register a listener of your choice
    launcher.registerTestExecutionListeners(listener);
    // Discover tests and build a test plan
    TestPlan testPlan = launcher.discover(request);
    // Execute test plan
    launcher.execute(testPlan);
    // Alternatively, execute the request directly
    launcher.execute(request);
}

TestExecutionSummary summary = listener.getSummary();
// Do something with the summary...

execute()メソッドには戻り値はありませんが、TestExecutionListenerを使用して結果を集計できます。例については、SummaryGeneratingListenerLegacyXmlReportGeneratingListener、およびUniqueIdTrackingListenerを参照してください。

すべてのTestExecutionListenerメソッドは順番に呼び出されます。開始イベントのメソッドは登録順に呼び出され、終了イベントのメソッドは逆順に呼び出されます。テストケースの実行は、すべてのexecutionStarted呼び出しが返されるまで開始されません。

6.4.3. TestEngineの登録

詳細については、TestEngineの登録に関する専用セクションを参照してください。

6.4.4. PostDiscoveryFilterの登録

LauncherDiscoveryRequestの一部としてLauncherAPIに渡される検出後フィルターを指定することに加えて、PostDiscoveryFilter実装は、JavaのServiceLoaderメカニズムを介して実行時に検出され、リクエストの一部であるものに加えて、Launcherによって自動的に適用されます。

たとえば、/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilterファイル内で宣言されたPostDiscoveryFilterを実装するexample.CustomTagFilterクラスは、自動的にロードおよび適用されます。

6.4.5. LauncherSessionListenerの登録

LauncherSessionListenerの登録された実装は、LauncherSessionが開かれたとき(Launcherが最初にテストを検出および実行する前)と、閉じられたとき(これ以上テストが検出または実行されないとき)に通知されます。これらは、LauncherFactoryに渡されるLauncherConfigを介してプログラムで登録するか、JavaのServiceLoaderメカニズムを介して実行時に検出して、LauncherSessionに自動的に登録することができます(自動登録が無効になっている場合を除く)。

ツールサポート

次のビルドツールとIDEは、LauncherSessionの完全なサポートを提供することが知られています。

  • Gradle 4.6以降

  • Maven Surefire/Failsafe 3.0.0-M6以降

  • IntelliJ IDEA 2017.3以降

他のツールも動作する可能性がありますが、明示的にテストされていません。

使用例

LauncherSessionListener は、ランチャーセッションにおける最初のテストの前と最後のテストの後にそれぞれ呼び出されるため、JVM ごとに一度だけ実行されるセットアップ/ティアダウンの動作を実装するのに適しています。ランチャーセッションのスコープは、使用する IDE やビルドツールによって異なりますが、通常はテスト JVM のライフサイクルに対応します。最初のテストを実行する前に HTTP サーバーを起動し、最後のテストが実行された後に停止するカスタムリスナーは、次のようになります。

src/test/java/example/session/GlobalSetupTeardownListener.java
package example.session;

import static java.net.InetAddress.getLoopbackAddress;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.sun.net.httpserver.HttpServer;

import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.LauncherSessionListener;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;

public class GlobalSetupTeardownListener implements LauncherSessionListener {

    private Fixture fixture;

    @Override
    public void launcherSessionOpened(LauncherSession session) {
        // Avoid setup for test discovery by delaying it until tests are about to be executed
        session.getLauncher().registerTestExecutionListeners(new TestExecutionListener() {
            @Override
            public void testPlanExecutionStarted(TestPlan testPlan) {
                if (fixture == null) {
                    fixture = new Fixture();
                    fixture.setUp();
                }
            }
        });
    }

    @Override
    public void launcherSessionClosed(LauncherSession session) {
        if (fixture != null) {
            fixture.tearDown();
            fixture = null;
        }
    }

    static class Fixture {

        private HttpServer server;
        private ExecutorService executorService;

        void setUp() {
            try {
                server = HttpServer.create(new InetSocketAddress(getLoopbackAddress(), 0), 0);
            }
            catch (IOException e) {
                throw new UncheckedIOException("Failed to start HTTP server", e);
            }
            server.createContext("/test", exchange -> {
                exchange.sendResponseHeaders(204, -1);
                exchange.close();
            });
            executorService = Executors.newCachedThreadPool();
            server.setExecutor(executorService);
            server.start(); (1)
            int port = server.getAddress().getPort();
            System.setProperty("http.server.host", getLoopbackAddress().getHostAddress()); (2)
            System.setProperty("http.server.port", String.valueOf(port)); (3)
        }

        void tearDown() {
            server.stop(0); (4)
            executorService.shutdownNow();
        }
    }

}
1 HTTP サーバーを起動する
2 テストで使用するために、ホストアドレスをシステムプロパティとしてエクスポートする
3 テストで使用するために、ポートをシステムプロパティとしてエクスポートする
4 HTTP サーバーを停止する

このサンプルでは、JDK に付属の jdk.httpserver モジュールの HTTP サーバー実装を使用していますが、他のサーバーやリソースでも同様に動作します。リスナーが JUnit Platform によって認識されるようにするには、次の名前と内容を持つリソースファイルをテストランタイムのクラスパスに追加して、サービスとして登録する必要があります (例えば、src/test/resources にファイルを追加するなど)。

src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener
example.session.GlobalSetupTeardownListener

これで、テストからリソースを使用できます。

src/test/java/example/session/HttpTests.java
package example.session;

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

import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;

import org.junit.jupiter.api.Test;

class HttpTests {

    @Test
    void respondsWith204() throws Exception {
        String host = System.getProperty("http.server.host"); (1)
        String port = System.getProperty("http.server.port"); (2)
        URL url = URI.create("http://" + host + ":" + port + "/test").toURL();

        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        int responseCode = connection.getResponseCode(); (3)

        assertEquals(204, responseCode); (4)
    }
}
1 リスナーによって設定されたシステムプロパティからサーバーのホストアドレスを読み取る
2 リスナーによって設定されたシステムプロパティからサーバーのポートを読み取る
3 サーバーにリクエストを送信する
4 レスポンスのステータスコードをチェックする

6.4.6. LauncherInterceptor の登録

Launcher および LauncherSessionListener のインスタンスの作成、および前者の discover メソッドと execute メソッドの呼び出しをインターセプトするために、クライアントは Java の ServiceLoader メカニズムを介して LauncherInterceptor のカスタム実装を登録できます。さらに、junit.platform.launcher.interceptors.enabled 設定パラメータtrue に設定する必要があります。

一般的なユースケースは、JUnit Platform がテストクラスとエンジン実装をロードするために使用する ClassLoader を置き換えるカスタムクラスを作成することです。

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;

import org.junit.platform.launcher.LauncherInterceptor;

public class CustomLauncherInterceptor implements LauncherInterceptor {

    private final URLClassLoader customClassLoader;

    public CustomLauncherInterceptor() throws Exception {
        ClassLoader parent = Thread.currentThread().getContextClassLoader();
        customClassLoader = new URLClassLoader(new URL[] { URI.create("some.jar").toURL() }, parent);
    }

    @Override
    public <T> T intercept(Invocation<T> invocation) {
        Thread currentThread = Thread.currentThread();
        ClassLoader originalClassLoader = currentThread.getContextClassLoader();
        currentThread.setContextClassLoader(customClassLoader);
        try {
            return invocation.proceed();
        }
        finally {
            currentThread.setContextClassLoader(originalClassLoader);
        }
    }

    @Override
    public void close() {
        try {
            customClassLoader.close();
        }
        catch (IOException e) {
            throw new UncheckedIOException("Failed to close custom class loader", e);
        }
    }
}

6.4.7. LauncherDiscoveryListener の登録

LauncherDiscoveryRequest の一部としてディスカバリーリスナーを指定したり、Launcher API を介してプログラムで登録したりすることに加えて、カスタムの LauncherDiscoveryListener 実装は、Java の ServiceLoader メカニズムを介してランタイム時に検出され、LauncherFactory を介して作成された Launcher に自動的に登録できます。

例えば、LauncherDiscoveryListener を実装し、/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener ファイル内で宣言された example.CustomLauncherDiscoveryListener クラスは、自動的にロードおよび登録されます。

6.4.8. TestExecutionListener の登録

テスト実行リスナーをプログラムで登録するための公開 Launcher API メソッドに加えて、カスタムの TestExecutionListener 実装は、Java の ServiceLoader メカニズムを介してランタイム時に検出され、LauncherFactory を介して作成された Launcher に自動的に登録されます。

例えば、TestExecutionListener を実装し、/META-INF/services/org.junit.platform.launcher.TestExecutionListener ファイル内で宣言された example.CustomTestExecutionListener クラスは、自動的にロードおよび登録されます。

6.4.9. TestExecutionListener の構成

TestExecutionListenerLauncher API を介してプログラムで登録される場合、リスナーは、例えばコンストラクタやセッターメソッドなどを介して、構成するためのプログラム的な方法を提供できます。ただし、TestExecutionListener が Java の ServiceLoader メカニズムを介して自動的に登録される場合 (「TestExecutionListener の登録」を参照)、ユーザーがリスナーを直接構成する方法はありません。このような場合、TestExecutionListener の作成者は、設定パラメータを介してリスナーを構成可能にすることを選択できます。リスナーは、testPlanExecutionStarted(TestPlan) および testPlanExecutionFinished(TestPlan) コールバックメソッドに提供される TestPlan を介して設定パラメータにアクセスできます。例については、UniqueIdTrackingListener を参照してください。

6.4.10. TestExecutionListener の非アクティブ化

特定の実行リスナーをアクティブにせずにテストスイートを実行すると便利な場合があります。たとえば、テスト結果をレポート目的で外部システムに送信するカスタム TestExecutionListener がある場合、デバッグ中にこれらの *debug* 結果がレポートされないようにすることができます。これを行うには、現在のテスト実行で非アクティブ化 (つまり、登録しない) する実行リスナーを指定するために、junit.platform.execution.listeners.deactivate *設定パラメータ* のパターンを指定します。

/META-INF/services/org.junit.platform.launcher.TestExecutionListener ファイル内の ServiceLoader メカニズムを介して登録されたリスナーのみを非アクティブ化できます。言い換えれば、LauncherDiscoveryRequest を介して明示的に登録された TestExecutionListener は、junit.platform.execution.listeners.deactivate *設定パラメータ* を介して非アクティブ化することはできません。

さらに、実行リスナーはテスト実行の開始前に登録されるため、junit.platform.execution.listeners.deactivate *設定パラメータ* は、JVM システムプロパティとして、または JUnit Platform 設定ファイル (詳細については「設定パラメータ」を参照) を介してのみ提供できます。この *設定パラメータ* は、Launcher に渡される LauncherDiscoveryRequest で提供することはできません。

パターンマッチング構文

詳細については、パターンマッチング構文を参照してください。

6.4.11. ランチャーの構成

テストエンジンとリスナーの自動検出および登録を細かく制御する必要がある場合は、LauncherConfig のインスタンスを作成し、それを LauncherFactory に提供できます。通常、LauncherConfig のインスタンスは、次の例に示すように、組み込みの Fluent *ビルダー* API を介して作成されます。

LauncherConfig launcherConfig = LauncherConfig.builder()
    .enableTestEngineAutoRegistration(false)
    .enableLauncherSessionListenerAutoRegistration(false)
    .enableLauncherDiscoveryListenerAutoRegistration(false)
    .enablePostDiscoveryFilterAutoRegistration(false)
    .enableTestExecutionListenerAutoRegistration(false)
    .addTestEngines(new CustomTestEngine())
    .addLauncherSessionListeners(new CustomLauncherSessionListener())
    .addLauncherDiscoveryListeners(new CustomLauncherDiscoveryListener())
    .addPostDiscoveryFilters(new CustomPostDiscoveryFilter())
    .addTestExecutionListeners(new LegacyXmlReportGeneratingListener(reportsDir, out))
    .addTestExecutionListeners(new CustomTestExecutionListener())
    .build();

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

try (LauncherSession session = LauncherFactory.openSession(launcherConfig)) {
    session.getLauncher().execute(request);
}

6.4.12. ドライランモード

Launcher API を介してテストを実行する場合、junit.platform.execution.dryRun.enabled 設定パラメータtrue に設定することで、*ドライランモード* を有効にできます。このモードでは、Launcher は実際にはテストを実行しませんが、登録された TestExecutionListener インスタンスに対して、すべてのテストがスキップされ、コンテナが成功したかのように通知します。これは、ビルドの構成の変更をテストしたり、すべてのテストが実行されるのを待たずに、リスナーが期待どおりに呼び出されることを確認したりするのに役立ちます。

6.5. テストエンジン

TestEngine は、特定のプログラミングモデルのテストの *ディスカバリー* と *実行* を容易にします。

例えば、JUnit は、JUnit Jupiter プログラミングモデルを使用して作成されたテストをディスカバリーして実行する TestEngine を提供します (「テストの記述」および「拡張モデル」を参照)。

6.5.1. JUnit テストエンジン

JUnit は、3 つの TestEngine 実装を提供します。

  • junit-jupiter-engine: JUnit Jupiter のコア。

  • junit-vintage-engine: JUnit 4 の上に薄いレイヤーを置き、JUnit Platform ランチャーインフラストラクチャで *vintage* テスト (JUnit 3.8 および JUnit 4 に基づく) を実行できるようにします。

  • junit-platform-suite-engine: JUnit Platform ランチャーインフラストラクチャを使用して、宣言型テストスイートを実行します。

6.5.2. カスタムテストエンジン

TestEngine を自分で作成するには、junit-platform-engine モジュールでインターフェースを実装し、エンジンを*登録*します。

すべての TestEngine は、独自の*ユニーク ID* を提供し、EngineDiscoveryRequest からテストを*ディスカバリー*し、ExecutionRequest に従ってそれらのテストを*実行*する必要があります。

junit- というユニーク ID プレフィックスは、JUnit Team の TestEngines 用に予約されています。

JUnit Platform Launcher は、JUnit Team によって公開された TestEngine 実装のみが、TestEngine ID に junit- プレフィックスを使用できることを強制します。

  • サードパーティの TestEnginejunit-jupiter または junit-vintage であると主張する場合、例外がスローされ、JUnit Platform の実行が直ちに停止します。

  • サードパーティの TestEngine が ID に junit- プレフィックスを使用する場合、警告メッセージがログに記録されます。JUnit Platform の後のリリースでは、このような違反に対して例外がスローされます。

JUnit Platform を起動する前に、IDE やツール内でのテストのディスカバリーを容易にするために、TestEngine 実装は @Testable アノテーションを使用することが推奨されています。例えば、JUnit Jupiter の @Test アノテーションと @TestFactory アノテーションは、@Testable でメタアノテーションされています。詳細については、@Testable の Javadoc を参照してください。

カスタム TestEngine を構成する必要がある場合は、ユーザーが 設定パラメータを介して構成を提供できるようにすることを検討してください。ただし、テストエンジンでサポートされているすべての構成パラメータに、一意のプレフィックスを使用することを強くお勧めします。そうすることで、構成パラメータの名前と他のテストエンジンの構成パラメータの名前の間に競合が発生しないようにします。また、設定パラメータは JVM システムプロパティとして提供される可能性があるため、他のシステムプロパティの名前との競合を避けるのが賢明です。例えば、JUnit Jupiter では、サポートされているすべての設定パラメータのプレフィックスとして junit.jupiter. を使用しています。さらに、TestEngine ID の junit- プレフィックスに関する上記の警告と同様に、独自の構成パラメータの名前に junit. をプレフィックスとして使用しないでください。

カスタム TestEngine を実装する方法に関する公式ガイドは現在ありませんが、JUnit テストエンジンの実装または、JUnit 5 wiki にリストされているサードパーティテストエンジンの実装を参照できます。また、カスタム TestEngine を作成する方法を示すさまざまなチュートリアルやブログをインターネット上で見つけることができます。

HierarchicalTestEngine は、テストのディスカバリーロジックのみを実装者が提供すればよい TestEngine SPI (junit-jupiter-engine で使用) の便利な抽象基本実装です。これは、並列実行のサポートを含む、Node インターフェイスを実装する TestDescriptors の実行を実装します。

6.5.3. TestEngine の登録

TestEngine の登録は、Java の ServiceLoader メカニズムを介してサポートされています。

例えば、junit-jupiter-engineモジュールは、junit-jupiter-engine JAR 内の /META-INF/services フォルダにある org.junit.platform.engine.TestEngine という名前のファイルに、自身の org.junit.jupiter.engine.JupiterTestEngine を登録します。

6.5.4. 要件

このセクションにおける「must」、「must not」、「required」、「shall」、「shall not」、「should」、「should not」、「recommended」、「may」、「optional」という単語は、RFC 2119 に記述されているように解釈されるものとします。
必須要件

ビルドツールやIDEとの相互運用性のために、TestEngine実装は以下の要件を遵守する必要があります。

  • TestEngine.discover()から返されるTestDescriptorは、TestDescriptorインスタンスのツリーのルートで*なければなりません*。これは、ノードとその子孫の間にサイクルが*あってはならない*ことを意味します。

  • TestEngineは、以前にTestEngine.discover()から生成して返したユニークIDについて、UniqueIdSelectorsを検出でき*なければなりません*。これにより、実行または再実行するテストのサブセットを選択できます。

  • TestEngine.execute()に渡されるEngineExecutionListenerexecutionSkippedexecutionStarted、およびexecutionFinishedメソッドは、TestEngine.discover()から返されるツリー内のすべてのTestDescriptorノードに対して、最大で1回呼び出され*なければなりません*。親ノードは、子ノードよりも前に開始として報告され、子ノードの後に終了として報告され*なければなりません*。ノードがスキップされたと報告された場合、その子孫に対してイベントが報告されては*なりません*。

拡張された互換性

以下の要件を遵守することはオプションですが、ビルドツールやIDEとの拡張された互換性のためには推奨されます。

  • 空の検出結果を示す場合を除き、TestEngine.discover()から返されるTestDescriptorは、完全に動的であるよりも、子を持っている*べき*です。これにより、ツールはテストの構造を表示したり、実行するテストのサブセットを選択したりできます。

  • UniqueIdSelectorsを解決する場合、TestEngineは、一致するユニークIDを持つTestDescriptorインスタンスとその祖先のみを返す*べき*ですが、選択されたテストの実行に必要な追加の兄弟ノードや他のノードを返しても*かまいません*。

  • TestEnginesは、テストの発見時にタグフィルターを適用できるように、テストとコンテナのタグ付けをサポートする*べき*です。

7. APIの進化

JUnit 5の主要な目標の1つは、多くのプロジェクトで使用されているにもかかわらず、JUnitを進化させるためのメンテナーの能力を向上させることです。 JUnit 4では、当初内部構造としてのみ追加された多くのものが、外部の拡張機能作成者やツールビルダーによって使用されるようになりました。そのため、JUnit 4の変更は特に困難であり、時には不可能になりました。

そのため、JUnit 5では、公開されているすべてのインターフェース、クラス、およびメソッドに対して定義されたライフサイクルが導入されています。

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

公開されたすべての成果物には、<major>.<minor>.<patch>というバージョン番号があり、公開されているすべてのインターフェース、クラス、およびメソッドには、@API@API Guardianプロジェクトからアノテーションされています。アノテーションのstatus属性には、以下のいずれかの値を割り当てることができます。

ステータス 説明

INTERNAL

JUnit自体以外のコードで使用してはなりません。予告なしに削除される可能性があります。

DEPRECATED

もはや使用するべきではありません。次のマイナーリリースで消える可能性があります。

EXPERIMENTAL

フィードバックを求めている新しい実験的な機能用です。
この要素は注意して使用してください。将来MAINTAINEDまたはSTABLEに昇格する可能性もありますが、パッチであっても予告なしに削除される可能性もあります。

MAINTAINED

現在のメジャーバージョンの次のマイナーリリースで、後方互換性のない方法で変更されない機能のためのものです。削除が予定されている場合、最初にDEPRECATEDに降格されます。

STABLE

現在のメジャーバージョン(5.*)で後方互換性のない方法で変更されない機能のためのものです。

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

7.2. 実験的なAPI

以下の表は、現在@API(status = EXPERIMENTAL)によって*実験的*と指定されているAPIをリストしています。このようなAPIに依存する場合は注意が必要です。

パッケージ名 型名 開始

org.junit.jupiter.api

Timeout.ThreadMode (enum)

5.9

org.junit.jupiter.api.extension

AnnotatedElementContext (インターフェース)

5.10

org.junit.jupiter.api.extension

DynamicTestInvocationContext (インターフェース)

5.8

org.junit.jupiter.api.extension

ExecutableInvoker (インターフェース)

5.9

org.junit.jupiter.api.extension

TestInstancePreConstructCallback (インターフェース)

5.9

org.junit.jupiter.api.io

CleanupMode (enum)

5.9

org.junit.jupiter.api.io

TempDirFactory (インターフェース)

5.10

org.junit.jupiter.params.converter

AnnotationBasedArgumentConverter (クラス)

5.10

org.junit.jupiter.params.provider

AnnotationBasedArgumentsProvider (クラス)

5.10

org.junit.platform.engine.discovery

IterationSelector (クラス)

1.9

org.junit.platform.engine.support.store

NamespacedHierarchicalStore (クラス)

5.10

org.junit.platform.engine.support.store

NamespacedHierarchicalStoreException (クラス)

5.10

org.junit.platform.jfr

FlightRecordingDiscoveryListener (クラス)

1.8

org.junit.platform.jfr

FlightRecordingExecutionListener (クラス)

1.8

org.junit.platform.launcher

LauncherDiscoveryListener (インターフェース)

1.6

org.junit.platform.launcher

LauncherInterceptor (インターフェース)

1.10

org.junit.platform.launcher

TestPlan.Visitor (インターフェース)

1.10

org.junit.platform.launcher.listeners

UniqueIdTrackingListener (クラス)

1.8

org.junit.platform.reporting.open.xml

OpenTestReportGeneratingListener (クラス)

1.9

org.junit.platform.suite.api

SelectMethod (アノテーション)

1.10

org.junit.platform.suite.api

SelectMethods (アノテーション)

1.10

7.3. 非推奨API

以下の表は、現在@API(status = DEPRECATED)によって*非推奨*と指定されているAPIをリストしています。非推奨のAPIは、今後のリリースで削除される可能性が高いため、可能な限り使用を避ける必要があります。

パッケージ名 型名 開始

org.junit.jupiter.api

MethodOrderer.Alphanumeric (クラス)

5.7

org.junit.platform.commons.util

BlacklistedExceptions (クラス)

1.7

org.junit.platform.commons.util

PreconditionViolationException (クラス)

1.5

org.junit.platform.engine.support.filter

ClasspathScanningSupport (クラス)

1.5

org.junit.platform.engine.support.hierarchical

SingleTestExecutor (クラス)

1.2

org.junit.platform.launcher.listeners

LegacyReportingUtils (クラス)

1.6

org.junit.platform.runner

JUnitPlatform (クラス)

1.8

org.junit.platform.suite.api

UseTechnicalNames (アノテーション)

1.8

7.4. @APIツールサポート

@API Guardianプロジェクトは、@APIでアノテーションされたAPIのパブリッシャーおよびコンシューマー向けにツールサポートを提供する予定です。たとえば、ツールサポートは、JUnit APIが@APIアノテーション宣言に従って使用されているかどうかを確認する手段を提供する可能性があります。

8. 貢献者

GitHubで直接現在の貢献者のリストを参照してください。

9. リリースノート

リリースノートはこちらで入手できます。

10. 付録

10.1. 再現可能なビルド

バージョン5.7以降、JUnit 5は、javadoc以外のJARが再現可能であることを目指しています。

Javaバージョンなどの同一のビルド条件下では、繰り返されるビルドは同じバイト単位の出力を提供する必要があります。

これは、誰もがMaven Central/Sonatype上のアーティファクトのビルド条件を再現し、ローカルで同じ出力アーティファクトを生成して、リポジトリ内のアーティファクトが実際にこのソースコードから生成されたことを確認できることを意味します。

10.2. 依存関係メタデータ

最終リリースおよびマイルストーンのアーティファクトはMaven Centralにデプロイされ、スナップショットアーティファクトはSonatypeのスナップショットリポジトリ/org/junitにデプロイされます。

10.2.1. JUnit Platform

  • グループID: org.junit.platform

  • バージョン: 1.10.2

  • アーティファクトID:

    junit-platform-commons

    JUnit Platformの共通APIとサポートユーティリティ。 @API(status = INTERNAL)でアノテーションされたAPIは、JUnitフレームワーク内でのみ使用することを目的としています。 *外部関係者による内部APIの使用はサポートされていません!*

    junit-platform-console

    コンソールからJUnit Platformでテストを発見および実行するためのサポート。詳細については、コンソールランチャーを参照してください。

    junit-platform-console-standalone

    すべての依存関係が含まれた実行可能なJARは、junit-platform-console-standaloneディレクトリの下のMaven Centralで提供されています。詳細については、コンソールランチャーを参照してください。

    junit-platform-engine

    テストエンジンのパブリックAPI。詳細については、TestEngineの登録を参照してください。

    junit-platform-jfr

    JUnit Platform上のJava Flight Recorderイベント用のLauncherDiscoveryListenerおよびTestExecutionListenerを提供します。詳細については、Flight Recorderのサポートを参照してください。

    junit-platform-launcher

    テストプランの構成と起動のためのパブリックAPI。通常はIDEやビルドツールで使用されます。詳細については、JUnit Platform Launcher APIを参照してください。

    junit-platform-reporting

    テストレポートを生成するTestExecutionListenerの実装。通常はIDEやビルドツールで使用されます。詳細については、JUnit Platform Reportingを参照してください。

    junit-platform-runner

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

    junit-platform-suite

    GradleやMavenなどのビルドツールでの依存関係管理を簡素化するために、junit-platform-suite-apiおよびjunit-platform-suite-engineへの依存関係を推移的にプルするJUnit Platform Suiteアーティファクト。

    junit-platform-suite-api

    JUnit Platformでテストスイートを構成するためのアノテーション。JUnit Platform Suite EngineJUnitPlatformランナーでサポートされています。

    junit-platform-suite-commons

    JUnit Platformでテストスイートを実行するための共通サポートユーティリティ。

    junit-platform-suite-engine

    JUnit Platformでテストスイートを実行するエンジン。ランタイムでのみ必要です。詳細については、JUnit Platform Suite Engineを参照してください。

    junit-platform-testkit

    指定されたTestEngineのテストプランを実行し、流れるようなAPIを使用して結果にアクセスして、期待される結果を検証するためのサポートを提供します。

10.2.2. JUnit Jupiter

  • グループ ID: org.junit.jupiter

  • バージョン: 5.10.2

  • アーティファクトID:

    junit-jupiter

    GradleやMavenなどのビルドツールで依存関係の管理を簡素化するために、junit-jupiter-apijunit-jupiter-paramsjunit-jupiter-engineへの依存関係を推移的に取り込むJUnit Jupiterのアグリゲータアーティファクトです。

    junit-jupiter-api

    テストの記述拡張機能のためのJUnit Jupiter APIです。

    junit-jupiter-engine

    JUnit Jupiterテストエンジン実装です。実行時にのみ必要です。

    junit-jupiter-params

    JUnit Jupiterにおけるパラメータ化されたテストのサポートです。

    junit-jupiter-migrationsupport

    JUnit 4からJUnit Jupiterへの移行をサポートします。JUnit 4の@Ignoreアノテーションのサポートおよび選択されたJUnit 4ルールを実行する場合にのみ必要です。

10.2.3. JUnit Vintage

  • グループ ID: org.junit.vintage

  • バージョン: 5.10.2

  • アーティファクトID:

    junit-vintage-engine

    JUnit Platformで旧来のJUnitテストを実行できるようにするJUnit Vintageテストエンジン実装です。旧来のテストには、JUnit 3またはJUnit 4 APIを使用して記述されたテスト、またはこれらのAPIに基づいて構築されたテストフレームワークを使用して記述されたテストが含まれます。

10.2.4. Bill of Materials (BOM)

以下のMaven座標で提供されるBill of Materials POMは、MavenまたはGradleを使用して上記の複数のアーティファクトを参照する場合に、依存関係管理を容易にするために使用できます。

  • グループ ID: org.junit

  • アーティファクトID: junit-bom

  • バージョン: 5.10.2

10.2.5. 依存関係

上記のアーティファクトのほとんどは、公開されているMaven POMで、以下の@API Guardian JARへの依存関係を持っています。

  • グループ ID: org.apiguardian

  • アーティファクトID: apiguardian-api

  • バージョン: 1.1.2

さらに、上記のアーティファクトのほとんどは、以下のOpenTest4J JARへの直接または推移的な依存関係を持っています。

  • グループ ID: org.opentest4j

  • アーティファクトID: opentest4j

  • バージョン: 1.3.0

10.3. 依存関係図

component diagram