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 IDEA 、 Eclipse 、 NetBeans 、 Visual Studio Codeを参照) およびビルドツール ( Gradle 、 Maven 、および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.3. サンプルプロジェクト
コピーして実験できるプロジェクトの完全な実例を確認するには、junit5-samples
リポジトリから始めるのが良いでしょう。junit5-samples
リポジトリは、JUnit Jupiter、JUnit Vintage、およびその他のテストフレームワークに基づいたサンプルプロジェクトのコレクションをホストしています。サンプルプロジェクトには、適切なビルドスクリプト (例: build.gradle
、pom.xml
など) があります。以下のリンクは、選択できる組み合わせの一部を強調表示しています。
-
Gradle と Java の場合は、
junit5-jupiter-starter-gradle
プロジェクトを確認してください。 -
Gradle と Kotlin を使用する場合は、
junit5-jupiter-starter-gradle-kotlin
プロジェクトを確認してください。 -
Gradle と Groovy を使用する場合は、
junit5-jupiter-starter-gradle-groovy
プロジェクトを確認してください。 -
Maven を使用する場合は、
junit5-jupiter-starter-maven
プロジェクトを確認してください。 -
Ant を使用する場合は、
junit5-jupiter-starter-ant
プロジェクトを確認してください。
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
パッケージにあります。
アノテーション | 説明 |
---|---|
|
メソッドがテストメソッドであることを示します。JUnit 4 の |
|
メソッドがパラメータ化されたテストであることを示します。このようなメソッドは、オーバーライドされない限り、継承されます。 |
|
メソッドが繰り返しテストのテストテンプレートであることを示します。このようなメソッドは、オーバーライドされない限り、継承されます。 |
|
メソッドが動的テストのテストファクトリーであることを示します。このようなメソッドは、オーバーライドされない限り、継承されます。 |
|
メソッドが、登録されたプロバイダーによって返される呼び出しコンテキストの数に応じて、複数回呼び出されるように設計されたテストケースのテンプレートであることを示します。このようなメソッドは、オーバーライドされない限り、継承されます。 |
|
アノテーション付きのテストクラス内の |
|
アノテーション付きのテストクラスのテストメソッドの実行順序を設定するために使用されます。JUnit 4 の |
|
アノテーション付きのテストクラスのテストインスタンスのライフサイクルを設定するために使用されます。このようなアノテーションは継承されます。 |
|
テストクラスまたはテストメソッドのカスタム表示名を宣言します。このようなアノテーションは継承されません。 |
|
テストクラスのカスタム表示名ジェネレーターを宣言します。このようなアノテーションは継承されます。 |
|
アノテーション付きのメソッドは、現在のクラスの 各 |
|
アノテーション付きのメソッドは、現在のクラスの 各 |
|
アノテーション付きのメソッドは、現在のクラスの すべて の |
|
アノテーション付きのメソッドは、現在のクラスの すべて の |
|
アノテーション付きのクラスが、非静的なネストされたテストクラスであることを示します。Java 8 から Java 15 では、「クラスごと」のテストインスタンスのライフサイクルが使用されていない限り、 |
|
クラスまたはメソッドレベルで、テストをフィルタリングするためのタグを宣言するために使用されます。TestNG のテストグループや JUnit 4 のカテゴリと同様です。このようなアノテーションは、クラスレベルでは継承されますが、メソッドレベルでは継承されません。 |
|
テストクラスまたはテストメソッドを無効にするために使用されます。JUnit 4 の |
|
テスト、テストファクトリー、テストテンプレート、またはライフサイクルメソッドの実行が指定された時間を超えた場合に、失敗させるために使用されます。このようなアノテーションは継承されます。 |
|
拡張機能を宣言的に登録するために使用されます。このようなアノテーションは継承されます。 |
|
フィールドを介して拡張機能をプログラムで登録するために使用されます。このようなフィールドは、シャドウイングされない限り、継承されます。 |
|
ライフサイクルメソッドまたはテストメソッドで、フィールドインジェクションまたはパラメータインジェクションを介して一時ディレクトリを提供するために使用されます。 |
一部のアノテーションは現在、実験的である可能性があります。詳細については、実験的 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.3. テストクラスとメソッド
テストメソッドとライフサイクルメソッドは、現在のテストクラス内でローカルに宣言することも、スーパークラスから継承することも、インターフェイスから継承することもできます(テストインターフェイスとデフォルトメソッドを参照)。さらに、テストメソッドとライフサイクルメソッドは abstract
にすることはできず、値を返してはなりません (値を返す必要がある @TestFactory
メソッドを除く)。
クラスとメソッドの可視性
テストクラス、テストメソッド、およびライフサイクルメソッドは 一般的に、テストクラスが別のパッケージのテストクラスによって拡張されている場合など、技術的な理由がない限り、テストクラス、テストメソッド、およびライフサイクルメソッドの |
次のテストクラスは、@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 | 動作 |
---|---|
|
JUnit Jupiter 5.0 がリリースされて以来、標準の表示名生成動作に一致します。 |
|
パラメータのないメソッドの末尾の括弧を削除します。 |
|
アンダースコアをスペースに置き換えます。 |
|
テストの名前と囲みクラスの名前を連結して、完全な文を生成します。 |
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
を実装するカスタムクラスの完全修飾名を指定できます。
要約すると、テストクラスまたはテストメソッドの表示名は、次の優先順位ルールに従って決定されます。
-
@DisplayName
アノテーションの値(存在する場合) -
@DisplayNameGeneration
アノテーションで指定されたDisplayNameGenerator
を呼び出す(存在する場合) -
構成パラメータで構成されたデフォルトの
DisplayNameGenerator
を呼び出す(存在する場合) -
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() を使用したプリエンプティブタイムアウト
この一般的な例の1つは、Spring Frameworkのトランザクションテストサポートです。具体的には、Springのテストサポートは、テストメソッドが呼び出される前に、トランザクションの状態を現在のスレッド( 同様の副作用は、 |
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チームはAssertJ、Hamcrest、Truthなどのサードパーティのアサーションライブラリを使用することを推奨しています。したがって、開発者は自分の選択したアサーションライブラリを自由に使用できます。
たとえば、マッチャーと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() {
}
}
|
|
2.8. 条件付きテスト実行
JUnit JupiterのExecutionCondition
拡張APIを使用すると、開発者は特定の条件に基づいてコンテナまたはテストをプログラムで有効化または無効化できます。このような条件の最も単純な例は、DisabledCondition
組み込みで、これは@Disabled
アノテーションをサポートしています(テストの無効化を参照)。@Disabled
に加えて、JUnit Jupiterは、org.junit.jupiter.api.condition
パッケージ内の他のいくつかのアノテーションベースの条件もサポートしており、開発者はコンテナとテストを宣言的に有効または無効にできます。複数のExecutionCondition
拡張機能が登録されている場合、いずれかの条件がdisabledを返すとすぐに、コンテナまたはテストは無効になります。無効にする理由の詳細を提供したい場合は、これらの組み込み条件に関連付けられたすべてのアノテーションに、その目的で使用できるdisabledReason
属性があります。
詳細については、ExecutionCondition
と次のセクションを参照してください。
複合アノテーション
次のセクションにリストされている条件付きアノテーションは、カスタムの複合アノテーションを作成するためにメタアノテーションとして使用することもできます。@EnabledOnOsデモの |
JUnit Jupiterの条件付きアノテーションは |
特に明記しない限り、次のセクションにリストされている各条件付きアノテーションは、特定のテストインターフェイス、テストクラス、またはテストメソッドで1回だけ宣言できます。条件付きアノテーションが、特定のエレメントに直接、間接的、またはメタ的に複数回存在する場合、JUnitによって最初に検出されたアノテーションのみが使用されます。追加の宣言はすべて無視されます。ただし、各条件付きアノテーションは、 |
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以降、 |
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 以降、 |
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 メソッドまたはインスタンスメソッドを条件メソッドとして使用できます。 |
ユーティリティクラスの既存の static メソッドをカスタム条件として使用できることはよくあります。 たとえば、
|
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
実装のいずれかを使用できます。
-
MethodOrderer.DisplayName
: 表示名に基づいて、テストメソッドをアルファベット順にソートします(表示名生成の優先順位ルールを参照)。 -
MethodOrderer.MethodName
: テストメソッドを、その名前と仮引数リストに基づいてアルファベット順にソートします。 -
MethodOrderer.OrderAnnotation
:@Order
アノテーションを介して指定された値に基づいて、テストメソッドを数値順にソートします。 -
MethodOrderer.Random
: テストメソッドを疑似ランダムに順序付け、カスタムシードの構成をサポートします。 -
MethodOrderer.Alphanumeric
: テストメソッドを、その名前と仮引数リストに基づいてアルファベット順にソートします。MethodOrderer.MethodName
を推奨しており、6.0 で削除される予定です。
次の例は、テストメソッドが @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
実装のいずれかを使用できます。
-
ClassOrderer.ClassName
: テストクラスを、その完全修飾クラス名に基づいてアルファベット順にソートします。 -
ClassOrderer.DisplayName
: 表示名に基づいて、テストクラスをアルファベット順にソートします(表示名生成の優先順位ルールを参照)。 -
ClassOrderer.OrderAnnotation
:@Order
アノテーションを介して指定された値に基づいて、テストクラスを数値順にソートします。 -
ClassOrderer.Random
: テストクラスを疑似ランダムに順序付け、カスタムシードの構成をサポートします。
たとえば、テストクラスで @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 のテスト実行ツリーは次の画像のようになります。

この例では、外側のテストの前提条件が、セットアップコードの階層的なライフサイクルメソッドを定義することにより、内側のテストで使用されます。たとえば、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
型の場合、RepetitionExtension
はRepetitionInfo
のインスタンスを提供します。RepetitionInfo
を使用すると、現在の繰り返し、繰り返しの合計回数、失敗した繰り返しの回数、および対応する@RepeatedTest
の失敗しきい値に関する情報を取得できます。ただし、RepetitionExtension
は@RepeatedTest
のコンテキスト外では登録されないことに注意してください。 繰り返しテストの例を参照してください。 -
TestReporterParameterResolver
: コンストラクタまたはメソッドのパラメータがTestReporter
型の場合、TestReporterParameterResolver
はTestReporter
のインスタンスを提供します。TestReporter
を使用して、現在のテスト実行に関する追加データを公開できます。データは、TestExecutionListener
のreportingEntryPublished()
メソッドを介して利用でき、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 10
、repetition 2 of 10
などになります。@RepeatedTest
メソッドの表示名を各繰り返しの名前に含める場合は、独自のカスタムパターンを定義するか、定義済みの RepeatedTest.LONG_DISPLAY_NAME
パターンを使用できます。後者は "{displayName} :: repetition {currentRepetition} of {totalRepetitions}"
と等しく、個々の繰り返しの表示名は repeatedTest() :: repetition 1 of 10
、repeatedTest() :: repetition 2 of 10
などになります。
現在の繰り返し、繰り返しの総数、失敗した繰り返しの数、および失敗しきい値に関する情報を取得するために、開発者は RepetitionInfo
のインスタンスを @RepeatedTest
、@BeforeEach
、または @AfterEach
メソッドに注入することを選択できます。
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 5
、Wiederholung 2 von 5
などの個々の繰り返しの名前になります。
beforeEach()
メソッドには @BeforeEach
アノテーションが付いているため、各反復テストの各繰り返しの前に実行されます。TestInfo
と RepetitionInfo
をメソッドに注入することで、現在実行中の反復テストに関する情報を取得できることがわかります。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
(たとえば、TestInfo
、TestReporter
などのインスタンスを取得するため)によって提供される場合もあります。具体的には、パラメータ化されたテストメソッドは、次の規則に従って仮パラメータを宣言する必要があります。
-
最初に 0 個以上のインデックス付き引数を宣言する必要があります。
-
次に、0 個以上のアグリゲーターを宣言する必要があります。
-
最後に、
ParameterResolver
によって提供される 0 個以上の引数を宣言する必要があります。
このコンテキストでは、インデックス付き引数とは、ArgumentsProvider
によって提供される Arguments
内の特定のインデックスの引数であり、メソッドの仮パラメータリスト内の同じインデックスでパラメータ化されたメソッドへの引数として渡されます。アグリゲーターとは、ArgumentsAccessor
型の任意のパラメータ、または @AggregateWith
アノテーションが付けられた任意のパラメータです。
AutoCloseable 引数
これを防ぐには、 |
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
メソッドは、それぞれ値 1
、2
、および 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.String
、java.util.Collection
(およびpublic
引数なしコンストラクターを持つ具象サブタイプ)、java.util.List
、java.util.Set
、java.util.SortedSet
、java.util.NavigableSet
、java.util.Map
(およびpublic
引数なしコンストラクターを持つ具象サブタイプ)、java.util.SortedMap
、java.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
メソッドの個々の呼び出しに対する物理的な引数として提供されます。一般的に言えば、これはArguments
のStream
(つまり、Stream<Arguments>
)に変換されます。ただし、実際の具体的な戻り値の型はさまざまな形式を取ることができます。このコンテキストでは、「ストリーム」とは、JUnitがStream
に確実に変換できるものであり、Stream
、DoubleStream
、LongStream
、IntStream
、Collection
、Iterator
、Iterable
、オブジェクトの配列、またはプリミティブの配列などです。ストリーム内の「引数」は、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");
}
プリミティブ型(DoubleStream
、IntStream
、およびLongStream
)のストリームも、次の例で示すようにサポートされています。
@ParameterizedTest
@MethodSource("range")
void testWithRangeMethodSource(int argument) {
assertNotEquals(9, argument);
}
static IntStream range() {
return IntStream.range(0, 20).skip(10);
}
パラメータ化されたテストメソッドが複数のパラメータを宣言する場合、以下に示すように、Arguments
インスタンスまたはオブジェクト配列のコレクション、ストリーム、または配列を返す必要があります(サポートされている戻り値の型に関する詳細については、@MethodSource
のJavadocを参照してください)。arguments(Object…)
は、Arguments
インターフェースで定義された静的なファクトリメソッドであることに注意してください。また、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
リテラル)として表現できます。@CsvSource
のvalue
属性を介して提供される各文字列は、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
に設定することで変更できます。
入力例 | 結果の引数リスト |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
使用しているプログラミング言語がテキストブロック(たとえば、Java SE 15以降)をサポートしている場合は、@CsvSource
のtextBlock
属性を代わりに使用できます。テキストブロック内の各レコードは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
に設定できます。以下の例では、numLinesToSkip
とuseHeadersInDisplayName
の使用法を示しています。
デフォルトの区切り文字はカンマ(,
)ですが、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);
}
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
を実装する場合(ValueArgumentsProvider
やCsvArgumentsProvider
などの組み込みプロバイダーのように)、AnnotationBasedArgumentsProvider
クラスを拡張できます。
2.16.4. 引数変換
拡大変換
JUnit Jupiterは、@ParameterizedTest
に提供される引数の拡大プリミティブ変換をサポートしています。たとえば、@ValueSource(ints = { 1, 2, 3 })
でアノテーションが付けられたパラメータ化されたテストは、int
型の引数だけでなく、long
、float
、またはdouble
型の引数も受け入れるように宣言できます。
暗黙的変換
@CsvSource
のようなユースケースをサポートするために、JUnit Jupiterは多くの組み込みの暗黙的な型コンバーターを提供します。変換プロセスは、各メソッドパラメータの宣言された型によって異なります。
たとえば、@ParameterizedTest
がTimeUnit
型のパラメータを宣言し、宣言されたソースによって提供される実際の型がString
である場合、文字列は対応するTimeUnit
Enum定数に自動的に変換されます。
@ParameterizedTest
@ValueSource(strings = "SECONDS")
void testWithImplicitArgumentConversion(ChronoUnit argument) {
assertNotNull(argument.name());
}
String
インスタンスは、次のターゲット型に暗黙的に変換されます。
10進数、16進数、および8進数のString リテラルは、それらの整数型(byte 、short 、int 、long 、およびそれらのボックス化された対応物)に変換されます。 |
ターゲット型 | 例 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
フォールバック 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 ✔
name
はMessageFormat
パターンであることに注意してください。したがって、表示するには、単一引用符('
)を二重の単一引用符(''
)として表す必要があります。
カスタム表示名では、次のプレースホルダーがサポートされています。
プレースホルダー | 説明 |
---|---|
|
メソッドの表示名 |
|
現在の呼び出しインデックス(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}
パラメータ化されたテストの表示名は、次の優先順位ルールに従って決定されます
-
存在する場合は、
@ParameterizedTest
のname
属性 -
存在する場合は、
junit.jupiter.params.displayname.default
構成パラメータの値 -
@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
インスタンスの Stream
、Collection
、Iterable
、Iterator
、または配列を返す必要があります。DynamicNode
のインスタンス化可能なサブクラスは、DynamicContainer
と DynamicTest
です。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
インスタンスの Collection
、Iterable
、Iterator
、配列、または 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
-
URI
にclasspath
スキームが含まれている場合 — たとえば、classpath:/test/foo.xml?line=20,column=2
。 DirectorySource
-
URI
がファイルシステムに存在するディレクトリを表す場合。 FileSource
-
URI
がファイルシステムに存在するファイルを表す場合。 MethodSource
-
URI
にmethod
スキームと完全修飾メソッド名 (FQMN) が含まれている場合 — たとえば、method:org.junit.Foo#bar(java.lang.String, java.lang.String[])
。FQMN のサポートされている形式については、DiscoverySelectors.selectMethod(String)
の Javadoc を参照してください。 ClassSource
-
URI
にclass
スキームと完全修飾クラス名が含まれている場合 — たとえば、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_THREAD
、SEPARATE_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]
。数値と単位の間のスペースは省略できます。単位を指定しない場合は、秒を使用することと同じです。
パラメータ値 | 同等のアノテーション |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 つのモードがあります: enabled
、disabled
、および 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
モードまたは MethodOrderer
(MethodOrderer.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.default
と junit.jupiter.execution.parallel.mode.classes.default
の 4 つの組み合わせすべて(最初の列のラベルを参照)について、クラスごとに 2 つのテストメソッドを持つ 2 つのトップレベルテストクラス A
と B
の実行がどのように動作するかを示しています。
junit.jupiter.execution.parallel.mode.classes.default
設定パラメータが明示的に設定されていない場合、junit.jupiter.execution.parallel.mode.default
の値が代わりに使用されます。
2.20.1. 構成
目的の並列度や最大プールサイズなどのプロパティは、ParallelExecutionConfigurationStrategy
を使用して構成できます。JUnit Platform は、すぐに利用できる 2 つの実装を提供しています: dynamic
と fixed
。あるいは、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 以降では、dynamic 、fixed 、および custom ストラテジーの最大プールサイズを制御することで、同時スレッドの最大数を制限できます。 |
関連プロパティ
次の表に、並列実行を構成するための関連プロパティを示します。このようなプロパティの設定方法の詳細については、構成パラメータを参照してください。
プロパティ | 説明 | サポートされる値 | デフォルト値 |
---|---|---|---|
|
並列テスト実行を有効にする |
|
|
|
テストツリー内のノードのデフォルトの実行モード |
|
|
|
トップレベルクラスのデフォルトの実行モード |
|
|
|
目的の並列度と最大プールサイズの実行ストラテジー |
|
|
|
|
正の小数 |
|
|
junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor |
|
1.0 以上の正の小数 |
|
junit.jupiter.execution.parallel.config.dynamic.saturate |
|
|
|
junit.jupiter.execution.parallel.config.fixed.parallelism |
|
正の整数 |
|
junit.jupiter.execution.parallel.config.fixed.max-pool-size |
|
|
|
junit.jupiter.execution.parallel.config.fixed.saturate |
|
|
|
junit.jupiter.execution.parallel.config.custom.class |
|
正の整数 |
例:org.example.CustomStrategy
以下の例のテストが、@ResourceLockを使用せずに並行して実行された場合、それらは不安定になります。同じJVMシステムプロパティへの書き込みと読み取りという固有の競合状態により、パスする場合もあれば、失敗する場合もあるでしょう。
共有リソースへのアクセスが@ResourceLock
アノテーションを使用して宣言されている場合、JUnit Jupiterエンジンはこの情報を使用して、競合するテストが並行して実行されないようにします。
テストを分離して実行する
ほとんどのテストクラスが同期なしで並行して実行できるが、分離して実行する必要があるテストクラスがいくつかある場合は、後者を |
共有リソースを一意に識別する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
属性というオプションがあり、NEVER
、ON_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
のようなインメモリファイルシステムを使用することも可能です。次の例は、それを実現する方法を示しています。
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
アノテーションが宣言されているフィールドまたはパラメータのメタアノテーションまたは追加のアノテーションは、ファクトリを構成するための追加の属性を公開する場合があります。このようなアノテーションと関連する属性は、createTempDirectory
のAnnotatedElementContext
パラメータを介してアクセスできます。
junit.jupiter.tempdir.factory.default
構成パラメータを使用して、デフォルトで使用するTempDirFactory
の完全修飾クラス名を指定できます。@TempDir
アノテーションのfactory
属性を介して構成されたファクトリの場合と同様に、提供されたクラスはTempDirFactory
インターフェースを実装する必要があります。アノテーションのfactory
属性で別のファクトリを指定しない限り、デフォルトのファクトリはすべての@TempDir
アノテーションに使用されます。
要約すると、一時ディレクトリのファクトリは、次の優先順位ルールに従って決定されます。
-
存在する場合、
@TempDir
アノテーションのfactory
属性 -
存在する場合、構成パラメータを介して構成されたデフォルトの
TempDirFactory
-
それ以外の場合、
org.junit.jupiter.api.io.TempDirFactory$Standard
が使用されます。
3. JUnit 4からの移行
JUnit Jupiterプログラミングモデルと拡張モデルは、Rules
やRunners
などの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.2. 移行のヒント
以下は、既存のJUnit 4テストをJUnit Jupiterに移行する際に注意する必要があるトピックです。
-
アノテーションは、
org.junit.jupiter.api
パッケージにあります。 -
アサーションは、
org.junit.jupiter.api.Assertions
にあります。 -
アサンプションは、
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
または他の組み込みの実行条件のいずれかを使用してください。-
JUnit 4 @Ignoreサポートも参照してください。
-
-
@Category
は存在しなくなりました。代わりに@Tag
を使用してください。 -
@RunWith
は存在しなくなりました。@ExtendWith
で置き換えられます。 -
@Rule
と@ClassRule
は存在しなくなりました。@ExtendWith
と@RegisterExtension
で置き換えられます。-
制限付きJUnit 4ルールサポートも参照してください。
-
-
@Test(expected = …)
およびExpectedException
ルールは存在しなくなりました。代わりにAssertions.assertThrows(…)
を使用してください。-
ExpectedException
を引き続き使用する必要がある場合は、制限付きJUnit 4ルールサポートを参照してください。
-
-
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
でオンにできます。このアノテーションは構成されたアノテーションであり、すべてのルール移行サポート拡張機能(VerifierSupport
、ExternalResourceSupport
、および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のメソッドassertEquals
はassertEquals(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-launcher
、junit-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-launcher
、junit-jupiter-engine
、およびjunit-vintage-engine
の対応するバージョンのJARをクラスパスに含める必要がある場合があります。
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")
<!-- ... -->
<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.gradle
のtest
タスク宣言内で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.platform
、org.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
の公式ドキュメントを参照してください。
あるいは、Log4jやLogbackなどの他のロギングフレームワークにログメッセージをリダイレクトすることもできます。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へのテスト依存関係を追加できます。
|
依存関係のバージョンの調整
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.platform
、org.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 が静的メンバークラスを除外しないようにするには、次のように除外ルールをオーバーライドできます。
<!-- ... -->
<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
です。依存関係のバージョンを変更するメカニズムは、Gradle とMaven の両方でドキュメント化されています。
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 ランナーは非推奨になりました
近年、すべての主流のビルドツールと IDE は、JUnit Platform で直接テストを実行するための組み込みサポートを提供しています。 さらに、 したがって、
|
JUnitPlatform
ランナーは、JUnit 4 ベースの Runner
であり、JUnit 4 環境で JUnit Platform でサポートされているプログラミングモデルを持つテスト(たとえば、JUnit Jupiter テストクラス)を実行できます。
クラスに @RunWith(JUnitPlatform.class)
アノテーションを付けると、JUnit 4 をサポートしているが、JUnit Platform を直接サポートしていない IDE およびビルドシステムで実行できます。
JUnit Platform には JUnit 4 にない機能があるため、ランナーは JUnit Platform 機能のサブセット、特にレポートに関してのみサポートできます(表示名と技術名を参照)。 |
4.4.1. セットアップ
クラスパスには、次のアーティファクトとその依存関係が必要です。グループ ID、アーティファクト ID、およびバージョンに関する詳細については、依存関係メタデータを参照してください。
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 で実行されているテストエンジンに、次のいずれかのメカニズムを介して提供できるテキストベースのキーと値のペアです。
-
Launcher
API に提供されるリクエストの構築に使用されるLauncherDiscoveryRequestBuilder
のconfigurationParameter()
およびconfigurationParameters()
メソッド。JUnit Platform によって提供されるツールのいずれかを介してテストを実行する場合、構成パラメータは次のように指定できます。-
コンソールランチャー:
--config
コマンドラインオプションを使用します。 -
Gradle:
systemProperty
またはsystemProperties
DSL を使用します。 -
Maven Surefire プロバイダー:
configurationParameters
プロパティを使用します。
-
-
JVM システムプロパティ。
-
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つでも付いているすべてのテスト、およびタグがまったく付いていないすべてのテストをそれぞれ選択します。これらの特別な式は、通常のタグと同様に、他の式と組み合わせることができます。
演算子 | 意味 | 結合性 |
---|---|---|
|
not |
右 |
|
and |
左 |
|
or |
左 |
複数の次元にわたってテストにタグを付けている場合、タグ式は、実行するテストを選択するのに役立ちます。テストタイプ(例:マイクロ、統合、エンドツーエンド)と機能(例:製品、カタログ、配送)でタグ付けする場合、次のタグ式が役立ちます。
タグ式 | 選択 |
---|---|
|
製品のすべてのテスト |
|
カタログのすべてのテストと配送のすべてのテスト |
|
カタログと配送の間の共通部分のすべてのテスト |
|
製品のすべてのテスト(ただし、*エンドツーエンド*テストを除く) |
|
製品または配送のすべてのマイクロテストまたは統合テスト |
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
:LauncherSession
が開閉されるときにイベントを受信します。 -
LauncherInterceptor
:LauncherSession
のコンテキストでテストの検出と実行をインターセプトします。 -
LauncherDiscoveryListener
: テストの検出中に発生するイベントを受信します。 -
TestExecutionListener
: テストの実行中に発生するイベントを受信します。
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 イベントを記録するには、次の操作が必要です。
-
Java 8 Update 262 以降、または Java 11 以降を使用していることを確認します。
-
テスト実行時にクラスパスまたはモジュールパスに
org.junit.platform.jfr
モジュール (junit-platform-jfr-1.10.2.jar
) を提供します。 -
テスト実行を開始するときに 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.junit
、jdk.internal.reflect
、および sun.reflect
パッケージからのすべての呼び出しはスタックトレースから削除されます。そのため、org.junit.jupiter.api.Assertions
または org.junit.jupiter.api.Assumptions
の呼び出しは決して除外されません。
さらに、JUnit Platform Launcher からの最初の呼び出し以前のすべての要素が削除されます。
5. 拡張モデル
5.1. 概要
JUnit 4 の競合する Runner
、TestRule
、および 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
をメタアノテーションとして使用できます。次に、@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
: コンストラクタとメソッドの挿入をサポートするため
理想的には、 ただし、JUnit Jupiter では現在、非静的フィールドで |
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 フィールドは、static または非静的のいずれかになります。@RegisterExtension フィールドの 静的フィールド と インスタンスフィールド に関するドキュメントも @ExtendWith フィールドに適用されます。 |
5.2.2. プログラムによる拡張機能登録
開発者は、テストクラスのフィールドに @RegisterExtension
をアノテーションすることにより、拡張機能をプログラムで登録できます。
拡張機能が @ExtendWith
を介して宣言的に登録されている場合、通常はアノテーションを介してのみ設定できます。対照的に、拡張機能が @RegisterExtension
を介して登録されている場合、拡張機能のコンストラクタ、静的ファクトリメソッド、またはビルダー API に引数を渡すなど、プログラムで設定できます。
拡張機能の登録順序
デフォルトでは、
|
@RegisterExtension フィールドは、(評価時に)null であってはなりませんが、static または非staticのどちらでも構いません。 |
静的フィールド
@RegisterExtension
フィールドがstatic
の場合、拡張機能は、@ExtendWith
を介してクラスレベルで登録された拡張機能の後に登録されます。このような静的拡張機能は、実装できる拡張APIに制限はありません。したがって、静的フィールドを介して登録された拡張機能は、BeforeAllCallback
、AfterAllCallback
、TestInstancePostProcessor
、およびTestInstancePreDestroyCallback
などのクラスレベルおよびインスタンスレベルの拡張APIと、BeforeEachCallback
などのメソッドレベルの拡張APIを実装できます。
次の例では、テストクラスのserver
フィールドは、WebServerExtension
でサポートされているビルダーパターンを使用してプログラムで初期化されます。構成されたWebServerExtension
は、クラスレベルで拡張機能として自動的に登録されます。たとえば、クラス内のすべてのテストの前にサーバーを起動し、クラス内のすべてのテストが完了した後にサーバーを停止するためです。さらに、@BeforeAll
または@AfterAll
でアノテーションが付けられた静的ライフサイクルメソッド、および@BeforeEach
、@AfterEach
、および@Test
メソッドは、必要に応じてserver
フィールドを介して拡張機能のインスタンスにアクセスできます。
class WebServerDemo {
@RegisterExtension
static WebServerExtension server = WebServerExtension.builder()
.enableSecurity(false)
.build();
@Test
void getProductList() {
WebClient webClient = new WebClient();
String serverUrl = server.getServerUrl();
// Use WebClient to connect to web server using serverUrl and verify response
assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());
}
}
Kotlinの静的フィールド
Kotlinプログラミング言語には、static
フィールドの概念はありません。ただし、コンパイラーに、Kotlinの@JvmStatic
アノテーションを使用してprivate static
フィールドを生成するように指示できます。Kotlinコンパイラーにpublic static
フィールドを生成させたい場合は、代わりに@JvmField
アノテーションを使用できます。
次の例は、前のセクションのWebServerDemo
を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
がテストインスタンスを後処理する機会を与えられた後(アノテーション付きフィールドで使用する拡張機能のインスタンスを注入する可能性があります)に登録されます。したがって、そのようなインスタンス拡張機能がBeforeAllCallback
、AfterAllCallback
、または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のグローバル拡張機能(TestInfo
、TestReporter
などのサポート)の後に拡張機能レジストリに追加されます。
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つのクラスに対して |
5.6. テストインスタンスの後処理
TestInstancePostProcessor
は、テストインスタンスを後処理したいExtensions
のためのAPIを定義します。
一般的なユースケースとしては、テストインスタンスへの依存性の注入、テストインスタンス上でのカスタム初期化メソッドの呼び出しなどが挙げられます。
具体的な例については、MockitoExtension
と SpringExtension
のソースコードを参照してください。
5.7. テストインスタンスの破棄前コールバック
TestInstancePreDestroyCallback
は、テストで使用された後、破棄される前にテストインスタンスを処理したいExtensions
のためのAPIを定義します。
一般的なユースケースとしては、テストインスタンスに注入された依存関係のクリーンアップ、テストインスタンス上でのカスタム終了処理メソッドの呼び出しなどが挙げられます。
5.8. パラメータ解決
ParameterResolver
は、実行時に動的にパラメータを解決するための Extension
API を定義します。
テストクラスのコンストラクタ、テストメソッド、またはライフサイクルメソッド(定義を参照)がパラメータを宣言する場合、パラメータはParameterResolver
によって実行時に解決される必要があります。ParameterResolver
は、組み込みのもの(TestInfoParameterResolver
を参照)であるか、ユーザーによって登録されたものである必要があります。一般的に、パラメータは名前、型、アノテーション、またはそれらの組み合わせによって解決できます。
パラメータの型のみに基づいてパラメータを解決するカスタムの ParameterResolver
を実装したい場合、そのようなユースケースのための汎用アダプタとして機能する TypeBasedParameterResolver
を拡張すると便利です。
具体的な例については、CustomTypeParameterResolver
、CustomAnnotationParameterResolver
、および MapOfListsTypeBasedParameterResolver
のソースコードを参照してください。
JDK 9 より前の JDK バージョンで したがって、
|
他の拡張機能は、 |
5.9. テスト結果処理
TestWatcher
は、テストメソッドの実行結果を処理したい拡張機能のための API を定義します。具体的には、TestWatcher
は以下のイベントに関するコンテキスト情報とともに呼び出されます。
-
testDisabled
:無効化されたテストメソッドがスキップされた後に呼び出されます -
testSuccessful
:テストメソッドが正常に完了した後に呼び出されます -
testAborted
:テストメソッドが中断された後に呼び出されます -
testFailed
:テストメソッドが失敗した後に呼び出されます
定義で提示されている「テストメソッド」の定義とは対照的に、このコンテキストでは、テストメソッドは任意の @Test メソッドまたは @TestTemplate メソッド(例えば、@RepeatedTest または @ParameterizedTest )を指します。 |
このインターフェースを実装する拡張機能は、クラスレベル、インスタンスレベル、またはメソッドレベルで登録できます。クラスレベルで登録された場合、TestWatcher
は、@Nested
クラス内のものを含む、含まれているすべてのテストメソッドに対して呼び出されます。メソッドレベルで登録された場合、TestWatcher
は、登録されたテストメソッドに対してのみ呼び出されます。
したがって、 |
クラスレベルで失敗があった場合(例えば、@BeforeAll
メソッドによって例外がスローされた場合)、テスト結果は報告されません。同様に、テストクラスが ExecutionCondition
(例えば、@Disabled
)を介して無効化された場合、テスト結果は報告されません。
他の拡張 API とは対照的に、TestWatcher
はテストの実行に悪影響を与えることは許可されていません。したがって、TestWatcher
API のメソッドによってスローされた例外は WARNING
レベルでログに記録され、伝播またはテスト実行の失敗は許可されません。
提供された |
5.10. テストライフサイクルコールバック
以下のインターフェースは、テスト実行ライフサイクルのさまざまな時点でテストを拡張するための API を定義します。例については以下のセクションを参照し、詳細については org.junit.jupiter.api.extension
パッケージ内のこれらの各インターフェースの Javadoc を参照してください。
複数の拡張APIの実装 拡張機能の開発者は、単一の拡張機能内でこれらのインターフェースを任意に実装できます。具体的な例については、SpringExtension のソースコードを参照してください。 |
5.10.1. テスト実行の前後のコールバック
BeforeTestExecutionCallback
と AfterTestExecutionCallback
は、テストメソッドが実行される直前と直後に実行される動作を追加したい Extensions
のための API を定義します。したがって、これらのコールバックは、タイミング、トレース、および同様のユースケースに適しています。@BeforeEach
メソッドと @AfterEach
メソッドの前後に呼び出されるコールバックを実装する必要がある場合は、代わりに BeforeEachCallback
と AfterEachCallback
を実装してください。
次の例は、これらのコールバックを使用して、テストメソッドの実行時間を計算してログに記録する方法を示しています。TimingExtension
は、テストの実行時間を計測してログに記録するために、BeforeTestExecutionCallback
と AfterTestExecutionCallback
の両方を実装します。
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
を登録するため、テストの実行時にこのタイミングが適用されます。
@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
のすべてのインスタンスを飲み込み、他の種類の例外は再スローする拡張機能を示しています。
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
は、メンバーおよびクラスの修飾子を操作するための静的ユーティリティメソッドを提供します。たとえば、メンバーが public
、private
、abstract
、static
などとして宣言されているかどうかを判断します。詳細については、ModifierSupport
のJavadocを参照してください。
5.16. ユーザーコードと拡張機能の相対的な実行順序
1つ以上のテストメソッドを含むテストクラスを実行すると、ユーザーが提供するテストおよびライフサイクルメソッドに加えて、多くの拡張機能コールバックが呼び出されます。
以下も参照してください: テストの実行順序 |
5.16.1. ユーザーコードと拡張機能コード
次の図は、ユーザーが提供するコードと拡張機能コードの相対的な順序を示しています。ユーザーが提供するテストおよびライフサイクルメソッドはオレンジ色で表示され、拡張機能によって実装されたコールバックコードは青色で表示されます。灰色のボックスは単一のテストメソッドの実行を示しており、テストクラスのすべてのテストメソッドに対して繰り返されます。

次の表は、ユーザーコードと拡張機能コードの図の16のステップをさらに説明しています。
ステップ | インターフェース/アノテーション | 説明 |
---|---|---|
1 |
インターフェース |
コンテナのすべてのテストが実行される前に実行される拡張機能コード |
2 |
アノテーション |
コンテナのすべてのテストが実行される前に実行されるユーザーコード |
3 |
インターフェース |
|
4 |
インターフェース |
各テストが実行される前に実行される拡張機能コード |
5 |
アノテーション |
各テストが実行される前に実行されるユーザーコード |
6 |
インターフェース |
|
7 |
インターフェース |
テストが実行される直前に実行される拡張機能コード |
8 |
アノテーション |
実際のテストメソッドのユーザーコード |
9 |
インターフェース |
テスト中にスローされた例外を処理するための拡張機能コード |
10 |
インターフェース |
テスト実行とその対応する例外ハンドラーの直後に実行される拡張機能コード |
11 |
アノテーション |
各テストが実行された後に実行されるユーザーコード |
12 |
インターフェース |
|
13 |
インターフェース |
各テストが実行された後に実行される拡張機能コード |
14 |
アノテーション |
コンテナのすべてのテストが実行された後に実行されるユーザーコード |
15 |
インターフェース |
|
16 |
インターフェース |
コンテナのすべてのテストが実行された後に実行される拡張機能コード |
最も単純なケースでは、実際のテストメソッドのみが実行されます(ステップ8)。他のすべてのステップは、対応するライフサイクルコールバックのユーザーコードまたは拡張機能サポートの存在に応じてオプションです。さまざまなライフサイクルコールバックの詳細については、各アノテーションと拡張機能の対応するJavadocを参照してください。
上記の表のユーザーコードメソッドのすべての呼び出しは、InvocationInterceptor
を実装することで追加的にインターセプトできます。
5.16.2. コールバックのラッピング動作
JUnit Jupiterは、BeforeAllCallback
、AfterAllCallback
、BeforeEachCallback
、AfterEachCallback
、BeforeTestExecutionCallback
、およびAfterTestExecutionCallback
などのライフサイクルコールバックを実装する複数の登録済み拡張機能に対して、常にラッピング動作を保証します。
つまり、Extension1
がExtension2
の前に登録されている2つの拡張機能 Extension1
と Extension2
がある場合、Extension1
によって実装された「before」コールバックは、Extension2
によって実装された「before」コールバックの前に実行されることが保証されます。同様に、同じ順序で登録された同じ2つの拡張機能がある場合、Extension1
によって実装された「after」コールバックは、Extension2
によって実装された「after」コールバックの後に実行されることが保証されます。したがって、Extension1
はExtension2
をラップすると言えます。
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
クラスから静的にインポートされたすべてのメソッドは、ユーザーが提供するコールバックメソッドと拡張機能のコールバックメソッドの実行順序を理解するのに役立つコンテキスト情報をログに記録します。
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);
}
}
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);
}
}
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()");
}
}
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
内で実際に何が起こるかをさらに詳しく説明するのに役立ちます。

JUnit Jupiter は、単一のテストクラスまたはテストインターフェース内で宣言された複数のライフサイクルメソッドの実行順序を保証しません。JUnit Jupiter が、そのようなメソッドをアルファベット順に呼び出すように見える場合があります。ただし、それは厳密には真実ではありません。順序付けは、単一のテストクラス内の @Test
メソッドの順序付けに類似しています。
単一のテストクラスまたはテストインターフェース内で宣言されたライフサイクルメソッドは、決定論的でありながら意図的にわかりにくいアルゴリズムを使用して順序付けられます。これにより、テストスイートの後続の実行でライフサイクルメソッドが同じ順序で実行されることが保証され、再現可能なビルドが可能になります。 |
さらに、JUnit Jupiter は、単一のテストクラスまたはテストインターフェース内で宣言された複数のライフサイクルメソッドのラッピング動作をサポートしません。
次の例は、この動作を示しています。具体的には、ローカルで宣言されたライフサイクルメソッドが実行される順序のために、ライフサイクルメソッドの構成が壊れています。
-
データベース接続が開かれる前にテストデータが挿入されるため、データベースへの接続に失敗します。
-
テストデータが削除される前にデータベース接続が閉じられるため、データベースへの接続に失敗します。
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
内で実際に何が起こるかをさらに詳しく説明するのに役立ちます。

前述の動作により、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 のビルドキャッシュを使用する場合に重要な、異なるマシン間でタスクを再配置できるようにするために使用されます。
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)
}
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、およびバージョンに関する詳細については、依存関係メタデータを参照してください。
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上で実行される特定のTestEngine
のTestPlan
を実行し、期待される結果を検証するために、fluent APIを介して結果にアクセスするサポートを提供します。このAPIへの主要なエントリポイントは、engine()
およびexecute()
という名前の静的ファクトリメソッドを提供するEngineTestKit
です。LauncherDiscoveryRequest
を構築するためのfluent APIを利用するために、engine()
のバリアントのいずれかを選択することをお勧めします。
Launcher APIからLauncherDiscoveryRequestBuilder を使用してLauncherDiscoveryRequest を構築する場合は、EngineTestKit のexecute() のバリアントのいずれかを使用する必要があります。 |
以下の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() テストメソッドでは、JupiterTestEngine とExampleTestCase クラスの両方がコンテナと見なされるため、started およびsucceeded 統計情報のカウントは2 です。 |
6.3.3. イベントの検証
たとえば、ExampleTestCase
のskippedTest()
メソッドがスキップされた理由を検証する場合は、次の方法で実行できます。
次の例の イベントに対するAssertJアサーションで使用できる条件の詳細については、 |
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つだけ含まれていることを検証します。 |
ExampleTestCase
のfailingTest()
メソッドからスローされた例外のタイプを検証する場合は、次の方法で実行できます。
イベントと実行結果に対するAssertJアサーションで使用できる条件の詳細については、 |
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()
メソッドを使用してこれを実現する方法を示しています。
|
順序付け要件の有無にかかわらず、部分的な一致を行う場合は、それぞれ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.out やSystem.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の例として、ConsoleLauncher
がjunit-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
を使用して結果を集計できます。例については、SummaryGeneratingListener
、LegacyXmlReportGeneratingListener
、およびUniqueIdTrackingListener
を参照してください。
すべてのTestExecutionListener メソッドは順番に呼び出されます。開始イベントのメソッドは登録順に呼び出され、終了イベントのメソッドは逆順に呼び出されます。テストケースの実行は、すべてのexecutionStarted 呼び出しが返されるまで開始されません。 |
6.4.3. TestEngineの登録
詳細については、TestEngineの登録に関する専用セクションを参照してください。
6.4.4. PostDiscoveryFilterの登録
LauncherDiscoveryRequest
の一部としてLauncher
APIに渡される検出後フィルターを指定することに加えて、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 サーバーを起動し、最後のテストが実行された後に停止するカスタムリスナーは、次のようになります。
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
にファイルを追加するなど)。
example.session.GlobalSetupTeardownListener
これで、テストからリソースを使用できます。
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 の構成
TestExecutionListener
が Launcher
API を介してプログラムで登録される場合、リスナーは、例えばコンストラクタやセッターメソッドなどを介して、構成するためのプログラム的な方法を提供できます。ただし、TestExecutionListener
が Java の ServiceLoader
メカニズムを介して自動的に登録される場合 (「TestExecutionListener の登録」を参照)、ユーザーがリスナーを直接構成する方法はありません。このような場合、TestExecutionListener
の作成者は、設定パラメータを介してリスナーを構成可能にすることを選択できます。リスナーは、testPlanExecutionStarted(TestPlan)
および testPlanExecutionFinished(TestPlan)
コールバックメソッドに提供される TestPlan
を介して設定パラメータにアクセスできます。例については、UniqueIdTrackingListener
を参照してください。
6.4.10. TestExecutionListener の非アクティブ化
特定の実行リスナーをアクティブにせずにテストスイートを実行すると便利な場合があります。たとえば、テスト結果をレポート目的で外部システムに送信するカスタム TestExecutionListener
がある場合、デバッグ中にこれらの *debug* 結果がレポートされないようにすることができます。これを行うには、現在のテスト実行で非アクティブ化 (つまり、登録しない) する実行リスナーを指定するために、junit.platform.execution.listeners.deactivate
*設定パラメータ* のパターンを指定します。
|
パターンマッチング構文
詳細については、パターンマッチング構文を参照してください。
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
|
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()
に渡されるEngineExecutionListener
のexecutionSkipped
、executionStarted
、および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
属性には、以下のいずれかの値を割り当てることができます。
ステータス | 説明 |
---|---|
|
JUnit自体以外のコードで使用してはなりません。予告なしに削除される可能性があります。 |
|
もはや使用するべきではありません。次のマイナーリリースで消える可能性があります。 |
|
フィードバックを求めている新しい実験的な機能用です。 |
|
現在のメジャーバージョンの次のマイナーリリースで、後方互換性のない方法で変更されない機能のためのものです。削除が予定されている場合、最初に |
|
現在のメジャーバージョン( |
@API
アノテーションが型に存在する場合、その型のすべてのパブリックメンバーにも適用されると見なされます。メンバーは、より低い安定性の異なるstatus
値を宣言することができます。
7.2. 実験的なAPI
以下の表は、現在@API(status = EXPERIMENTAL)
によって*実験的*と指定されているAPIをリストしています。このようなAPIに依存する場合は注意が必要です。
パッケージ名 | 型名 | 開始 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7.3. 非推奨API
以下の表は、現在@API(status = DEPRECATED)
によって*非推奨*と指定されているAPIをリストしています。非推奨のAPIは、今後のリリースで削除される可能性が高いため、可能な限り使用を避ける必要があります。
パッケージ名 | 型名 | 開始 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 EngineとJUnitPlatformランナーでサポートされています。
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-api
、junit-jupiter-params
、junit-jupiter-engine
への依存関係を推移的に取り込むJUnit Jupiterのアグリゲータアーティファクトです。 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に基づいて構築されたテストフレームワークを使用して記述されたテストが含まれます。