1. 概要
このドキュメントの目的は、テストを書くプログラマや機能を拡張する開発者(extension authors)、 駆動部分の開発者(engine authors)ならびにビルドツールやIDEのベンダーのために、包括的な参考資料を提供することです。
このドキュメントは PDF でも利用可能です。
Translations
このドキュメントは 簡体字中国語 でも利用可能です。 |
1.1. JUnit 5とは?
これまでのバージョンのJUnitとは異なり、JUnit 5は3つのサブプロジェクトで 開発されている様々なモジュールから構成されています。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform は、JVM上で テスティングフレームワークを起動させる ための基礎としての機能を果たします。
また、プラットフォーム上で動作するテスティングフレームワークを開発するための TestEngine
APIを定義しています。
さらに、プラットフォーム上であらゆる TestEngine
を実行するために、 JUnit 4ベースのRuunner と同様に、
コマンドラインからプラットフォームを立ち上たり、 Gradle や
Maven 向けプラグインを構築するためのコンソールラウンチャーを提供します。
JUnit Jupiter は、JUnit 5でテストを書いたり拡張するための
新しい プログラミングモデル と 拡張モデルの組み合わせです。
Jupiterのサブプロジェクトは、プラットフォーム上でJupiterベースのテストを実行するための TestEngine
を提供します。
JUnit Vintage は、プラットフォーム上でJUnit3とJUnit4ベースのテストを実行するための
TestEngine
を提供します。
1.2. サポートしているJavaのバージョン
JUnit 5は実行時にJava 8(またはそれ以上)を必要とします。しかしながら、それよりも前のバージョンのJDKでコンパイルされたコードもテスト可能です。
1.3. ヘルプ
JUnit 5に関連した質問は Stack Overflow に投稿するか、 Gitter でチャットしてください。
2. インストール
最終的なリリースやマイルストーンのためのアーティファクトは、Maven Centralにデプロイされます。
スナップショット版はSonatypeの スナップショットレポジトリ の /org/junit 以下にデプロイされます。
2.1. 依存関係のメタデータ
2.1.1. JUnit Platform
-
Group ID:
org.junit.platrform
-
Version:
1.2.0
-
Articfact IDs:
-
junit-platform-commons
: JUnitの内部共通ライブラリ・ユーティリティ。これらのユーティリティは、JUnitフレームワーク内部使用のみが意図されています。 外部パーティからのいかなる使用はサポートされていません。 使用は自身の責任で行ってください! -
junit-platform-console
: コンソールからJUnit Platform上でテストを発見し実行することをサポートします。 詳細については Console Launcher をご覧ください。 -
junit-platform-console-standalone
: 全ての依存関係が包含された実行可能な JAR ファイルが Maven Central の junit-platform-standalone 以下のディレクトリで提供されています。 詳細については Console Launcher をご覧ください。 -
junit-platform-engine
: テストエンジンのパブリックAPIです。 詳細については 自分自身のテストエンジンをプラグインする をご覧ください。 -
junit-platform-gradle-plugin
: Gralde を用いたJUnit Platform上の テストの発見・実行をサポートします。 -
junit-platform-launcher
: テストプランの設定・起動するためのパブリックAPIです。 典型的にはIDEやビルドツールによって使われます。 詳細については JUnit Platform Launcher API をご覧ください。 -
junit-platform-runner
: JUnit 4環境におけるJUnit Platform上でテスト・テストスイートを実行するためのランナーです。 詳細についてはJUnit Platformを実行するためにJUnit 4を使用する をご覧ください。 -
junit-platform-suite-api
: JUnit Platform上でテストスイートを設定をするためのアノテーションです。 JUnitPlatform runner にサポートされています。 また、サードパーティによるTestEngine
実装にもサポートされている可能性があります。 -
junit-platform-surefire-provider
: Maven Surefire を用いたJUnit Platform上のテストの発見・実行をサポートします。
-
2.1.2. JUnit Jupiter
-
Group ID:
org.junit.jupiter
-
Version:
5.2.0
-
Articfact IDs:
2.1.3. JUnit Vintage
-
Group ID:
org.junit.vintage
-
Version:
5.2.0
-
Articfact ID:
-
junit-vintage-engine
: JUnit Vintageテストエンジンの実装です。 かつてのJUnitテスト(つまり、JUnit 3やJUnit 4形式で書かれたテスト)を新しいJUnit Platform上で実行できます。
-
2.1.4. 部品表
-
Group ID:
org.junit
-
Articfact ID:
junit-bom
-
Version:
5.2.0
2.1.5. 依存関係
上記のアーティファクトは全て、次の @API Guardian JAR で公開されているMaven POMsに依存関係があります。
- Group ID: org.apiguardian
- Articfact ID: apiguardian-api
- Version: {apiguardian-version}
さらに、上記アーティファクトのほとんどは、次の OpenTest4J JARと直接的、または推移的な依存関係があります。
-
Group ID:
org.opentest4j
-
Articfact ID:
opentest4j
-
Version:
5.2.0
2.2. 依存関係図
2.3. JUnit Jupiter サンプルプロジェクト
junit5-samples レポジトリは、
JUnit JupiterとJUnit Vintageをベースとしたサンプルプロジェクト集をホストしています。
下記プロジェクトには build.gradle
と pom.xml
がそれぞれ置かれています。
-
Gradleに関しては、 junit5-jupiter-starter-gradle を確認してください。
-
Mavenに関しては、 junit5-jupiter-starter-maven を確認してください。
3. テストを書く
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class FirstJUnit5Tests {
@Test
void myFirstTest() {
assertEquals(2, 1 + 1);
}
}
3.1. アノテーション
JUnit Jupiterは、テストの設定やフレームワークの拡張のために、 次のアノテーションをサポートしています。
全てのコアなアノテーションは、 junit-jupiter-api
モジュール内の
org.junit.jupiter.api
パッケージにあります。
アノテーション | 説明 |
---|---|
|
テストメソッドであることを意味します。JUnit 4の |
|
パラメータ化テスト であることを意味します。メソッドは オーバーライド されない限り、 継承 されます。 |
|
繰り返しテスト のテンプレートメソッドであることを意味します。メソッドは オーバーライド されない限り、 継承 されます。 |
|
動的テスト のファクトリーメソッドであることを意味します。メソッドは オーバーライド されない限り、 継承 されます。 |
|
アノテーションが付与されたテストクラスの テストインスタンス・ライフサイクル を設定するために使用されます。アノテーションは 継承 されます。 |
|
テストケースのテンプレート メソッドであることを意味します。テンプレートは登録された プロバイダ によって返される呼び出しコンテキストの数に応じて複数回呼び出されます。メソッドは オーバーライド されない限り、 継承 されます。 |
|
テストクラスもしくはテストメソッドのカスタム表示名を宣言します。アノテーションは 継承 されません。 |
|
現在のクラス内にある 各テスト ( |
|
現在のクラス内にある 各テスト ( |
|
現在のクラス内にある 全テスト ( |
|
現在のクラス内にある 全テスト ( |
|
ネストされた非staticなテストクラスであることを意味します。 |
|
クラスもしくはメソッドレベルでテストをフィルタリングするための タグ を宣言できます。TestNGのtest groupsもしくはJUnit 4のCategoriesと類似したものです。アノテーションはクラスレベルでは 継承 されますが、メソッドレベルでは 継承 されません。 |
|
テストクラスもしくはテストメソッドを 無効化 できます。JUnit 4の |
|
カスタム 拡張 を登録できます。アノテーションは 継承 されます。 |
次のアノテーションをつけたメソッドは値を返してはいけません。
( @Test
, @TestTemplate
, @RepeatedTest
, @BeforeAll
, @AfterAll
, @BeforeEach
, @AfterEach
)
いくつかのアノテーションは現在 実験段階 である恐れがあります。 詳細に関しては、 実験的なAPIs をご覧ください。 |
3.1.1. メタアノテーションと合成アノテーション
JUnit Jupiterアノテーションはメタアノテーションとして使うことができます。 つまり、メタアノテーションのセマンティックを自動で 継承 する 独自の 合成アノテーション を定義できます。
例えば、コードベースに @Tag("fast")
(タグ付けとフィルタリング をご覧ください。)を
コピー&ペーストする代わりに、次のように @Fast
というカスタム 合成アノテーション を作成できます。
@Fast
は @Tag("fast")
の代替として利用できます。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Tag;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface Fast {
}
3.2. テストクラスとメソッド
テストメソッド とは、直接もしくはメタ的に @Test
または @RepeatedTest
、 @ParamterizedTest
、 @TsetFactory
、 @TestTemplate
が
付与されたインスタンスメソッドです。 テストクラス とは、少なくとも1つのテストメソッドを含むトップレベルまたは静的なメンバークラスです。
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class StandardTests {
@BeforeAll
static void initAll() {
}
@BeforeEach
void init() {
}
@Test
void succeedingTest() {
}
@Test
void failingTest() {
fail("a failing test");
}
@Test
@Disabled("for demonstration purposes")
void skippedTest() {
// not executed
}
@AfterEach
void tearDown() {
}
@AfterAll
static void tearDownAll() {
}
}
テストクラスもテストメソッドも |
3.3. 表示名
テストクラスとテストメソッドはカスタム表示名(スペースや特殊文字、絵文字も使用可能です) を宣言できます。 それらがテストランナーとテストレポートによって表示されます。
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@DisplayName("A special test case")
class DisplayNameDemo {
@Test
@DisplayName("Custom test name containing spaces")
void testWithDisplayNameContainingSpaces() {
}
@Test
@DisplayName("╯°□°)╯")
void testWithDisplayNameContainingSpecialCharacters() {
}
@Test
@DisplayName("😱")
void testWithDisplayNameContainingEmoji() {
}
}
3.4. アサーション
JUnit Jupiterには、JUnit 4のアサーションメソッドの多くを備えています。
また、いくつかはJava 8のラムダ式で使うことができます。
全てのJUnit Jupiterアサーションは、 org.junit.jupiter.api.Assertions
クラスの static
メソッドです。
import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class AssertionsDemo {
@Test
void standardAssertions() {
assertEquals(2, 2);
assertEquals(4, 4, "The optional assertion message is now the last parameter.");
assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- "
+ "to avoid constructing complex messages unnecessarily.");
}
@Test
void groupedAssertions() {
// In a grouped assertion all assertions are executed, and any
// failures will be reported together.
assertAll("person",
() -> assertEquals("John", person.getFirstName()),
() -> assertEquals("Doe", person.getLastName())
);
}
@Test
void dependentAssertions() {
// Within a code block, if an assertion fails the
// subsequent code in the same block will be skipped.
assertAll("properties",
() -> {
String firstName = person.getFirstName();
assertNotNull(firstName);
// Executed only if the previous assertion is valid.
assertAll("first name",
() -> assertTrue(firstName.startsWith("J")),
() -> assertTrue(firstName.endsWith("n"))
);
},
() -> {
// Grouped assertion, so processed independently
// of results of first name assertions.
String lastName = person.getLastName();
assertNotNull(lastName);
// Executed only if the previous assertion is valid.
assertAll("last name",
() -> assertTrue(lastName.startsWith("D")),
() -> assertTrue(lastName.endsWith("e"))
);
}
);
}
@Test
void exceptionTesting() {
Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("a message");
});
assertEquals("a message", exception.getMessage());
}
@Test
void timeoutNotExceeded() {
// The following assertion succeeds.
assertTimeout(ofMinutes(2), () -> {
// Perform task that takes less than 2 minutes.
});
}
@Test
void timeoutNotExceededWithResult() {
// The following assertion succeeds, and returns the supplied object.
String actualResult = assertTimeout(ofMinutes(2), () -> {
return "a result";
});
assertEquals("a result", actualResult);
}
@Test
void timeoutNotExceededWithMethod() {
// The following assertion invokes a method reference and returns an object.
String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting);
assertEquals("Hello, World!", actualGreeting);
}
@Test
void timeoutExceeded() {
// The following assertion fails with an error message similar to:
// execution exceeded timeout of 10 ms by 91 ms
assertTimeout(ofMillis(10), () -> {
// Simulate task that takes more than 10 ms.
Thread.sleep(100);
});
}
@Test
void timeoutExceededWithPreemptiveTermination() {
// The following assertion fails with an error message similar to:
// execution timed out after 10 ms
assertTimeoutPreemptively(ofMillis(10), () -> {
// Simulate task that takes more than 10 ms.
Thread.sleep(100);
});
}
private static String greeting() {
return "Hello, World!";
}
}
また、JUnit Jupiterのいくつかのアサーションメソッドは
Kotlin で使うことができます。
全てのJUnit Jupiter Kotlinアサーションは、 org.junit.jupiter.api
パッケージのトップレベル関数です。
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.assertThrows
class AssertionsKotlinDemo {
@Test
fun `grouped assertions`() {
assertAll("person",
{ assertEquals("John", person.firstName) },
{ assertEquals("Doe", person.lastName) }
)
}
@Test
fun `exception testing`() {
val exception = assertThrows<IllegalArgumentException> ("Should throw an exception") {
throw IllegalArgumentException("a message")
}
assertEquals("a message", exception.message)
}
@Test
fun `assertions from a stream`() {
assertAll(
"people with name starting with J",
people
.stream()
.map {
// This mapping returns Stream<() -> Unit>
{ assertTrue(it.firstName.startsWith("J")) }
}
)
}
@Test
fun `assertions from a collection`() {
assertAll(
"people with last name of Doe",
people.map { { assertEquals("Doe", it.lastName) } }
)
}
}
3.4.1. サードパーティのアサーションライブラリ
JUnit Jupiterによって提供されているアサーション機能は多くのテストシナリオで十分ですが、 matchers といったより強力で追加的な機能が求められたり必要な場合があります。 そのような場合、JUnitチームは、 AssertJ や Hamcrest 、 Truth などといった サードパーティのアサーションライブラリの使用をお薦めします。 したがって、開発者は自由に選んだアサーションライブラリを使うことができます。
例えば、 matchers と流暢なAPI(fluent API)の組み合わせは、
アサーションをよりわかりやすく、読みやすくするために使うことができます。
しかしながら、JUnit Jupiterの org.junit.jupiter.api.Assertions
クラスは、Hamcrestの Matcher
を
許容しているJUnit 4の org.junit.Assert
クラスにあるような assertThat()
メソッドを提供していません。
代わりに、開発者はサードパーティのアサーションライブラリによって提供されているマッチャー用の組み込みサポートを使うことが奨励されています。
次の例は、JUnit JupiterのテストにおいてHamcrestから assertThat()
のサポートを使う方法を説明しています。
Hamcrestライブラリがクラスパスに加えられている限り、 assertThat()
や is()
、 equalTo()
といった
メソッドを静的にインポートできます。また、それらをテストの中で、下に示す assertWithHamcrestMatcher()
のように使うことができます。
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import org.junit.jupiter.api.Test;
class HamcrestAssertionDemo {
@Test
void assertWithHamcrestMatcher() {
assertThat(2 + 1, is(equalTo(3)));
}
}
当然、JUnit 4のプログラミングモデルに基づいたレガシーテストも
org.junit.Assert#assertThat
を用いて継続して利用可能です。
3.5. アサンプション
JUnit Jupiterは、JUnit 4のアサンプションメソッドのサブセットを備えています。
また、いくつかはJava 8のラムダ式で使うことができます。
全てのJUnit Jupiterアサンプションは、 org.junit.jupiter.api.Assumptions
クラスの static
メソッドです。
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumingThat;
import org.junit.jupiter.api.Test;
class AssumptionsDemo {
@Test
void testOnlyOnCiServer() {
assumeTrue("CI".equals(System.getenv("ENV")));
// remainder of test
}
@Test
void testOnlyOnDeveloperWorkstation() {
assumeTrue("DEV".equals(System.getenv("ENV")),
() -> "Aborting test: not on developer workstation");
// remainder of test
}
@Test
void testInAllEnvironments() {
assumingThat("CI".equals(System.getenv("ENV")),
() -> {
// perform these assertions only on the CI server
assertEquals(2, 2);
});
// perform these assertions in all environments
assertEquals("a string", "a string");
}
}
3.6. テストの無効化
テストクラス全体もしくは各テストメソッドは、 @Disabled
アノテーション
または 条件付きテスト実行 で議論されているアノテーションの1つ、
カスタム ExecutionCondition
によって 無効化 できます。
これは @Disabled
テストクラスです。
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled
class DisabledClassDemo {
@Test
void testWillBeSkipped() {
}
}
そして、これは @Disabled
テストメソッドを含むテストクラスです。
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class DisabledTestsDemo {
@Disabled
@Test
void testWillBeSkipped() {
}
@Test
void testWillBeExecuted() {
}
}
3.7. 条件付きテスト実行
JUnit Jupiterの ExecutionCondition
拡張APIを用いて、
ある条件に基づいたコンテナまたはテストを プログラム的に 有効 または 無効 にできます。
そのような条件の最も単純な例は、@Disabled
アノテーションをサポートしている
組み込みの DisabledCondition
です( テストの無効化 をご覧ください)。
@Disabled
に加えて、JUnit Jupiterは、 org.junit.jupiter.api.condition
パッケージに
他のいくつかのアノテーションベースの条件もサポートしており、コンテナやテストを 宣言的に
有効 または 無効 にできます。詳細については、次章をご覧ください。
合成アノテーション
次章に列挙する 条件 アノテーションはいずれも、カスタム 合成アノテーション を作るために
メタアノテーションとしても使える可能性があります。
例えば、 @EnabledOnOsのデモ にある
|
次章に列挙する 条件 アノテーションはそれぞれ、
テストインターフェイスまたはテストクラス、テストメソッドに一度だけ宣言できます。
もし条件アノテーションがある要素に直接的か間接的、またはメタ的に複数存在する場合、
JUnitによって発見された最初のアノテーションのみ使われます
(いかなる追加的なアノテーションも静かに無視されます)。
しかしながら、 |
3.7.1. オペレーティングシステムに関する条件
@EnabledOnOs
と @DisabledOnOs
アノテーションを使うことで、
特定のオペーティングシステム上でコンテナまたはテストを有効にしたり無効にできます。
@Test
@EnabledOnOs(MAC)
void onlyOnMacOs() {
// ...
}
@TestOnMac
void testOnMac() {
// ...
}
@Test
@EnabledOnOs({ LINUX, MAC })
void onLinuxOrMac() {
// ...
}
@Test
@DisabledOnOs(WINDOWS)
void notOnWindows() {
// ...
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@EnabledOnOs(MAC)
@interface TestOnMac {
}
3.7.2. Java実行環境に関する条件
@EnabledOnJre
と @DisabledOnJre
アノテーションを使うことで、
特定のバージョンのJava実行環境(JRE)上でコンテナまたはテストを有効にしたり無効にできます。
@Test
@EnabledOnJre(JAVA_8)
void onlyOnJava8() {
// ...
}
@Test
@EnabledOnJre({ JAVA_9, JAVA_10 })
void onJava9Or10() {
// ...
}
@Test
@DisabledOnJre(JAVA_9)
void notOnJava9() {
// ...
}
3.7.3. システムプロパティに関する条件
@EnabledIfSystemProperty
と @DisabledIfSystemProperty
アノテーションを使うことで、
named
で指定したJVMシステムプロパティの値に応じて、
コンテナまたはテストを有効にしたり無効にできます。
matches
属性を使うことで、値は正規表現として解釈されます。
@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
void onlyOn64BitArchitectures() {
// ...
}
@Test
@DisabledIfSystemProperty(named = "ci-server", matches = "true")
void notOnCiServer() {
// ...
}
3.7.4. 環境変数に関する条件
@EnabledIfEnvironmentVariable
と @DisabledIfEnvironmentVariable
アノテーションを使うことで、
基礎となるオペレーティングシステムからの named
で指定した環境変数の値に応じて、
コンテナまたはテストを有効にしたり無効にできます。
matches
属性を使うことで、値は正規表現として解釈されます。
@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server")
void onlyOnStagingServer() {
// ...
}
@Test
@DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")
void notOnDeveloperWorkstation() {
// ...
}
3.7.5. スクリプトベースの条件
JUnit Jupiterは、 @EnabledIf
と @DisabledIf
アノテーションを使うことで、
設定されたスクリプトの評価値に応じて、コンテナまたはテストを有効にしたり無効にできる機能を提供しています。
スクリプトは、JavaScriptまたはGroovy、
JSR 223で定義されているJava Scripting APIをサポートしているスクリプト言語であれば記述できます。
@EnabledIf と @DisabledIf を使った条件付きテストテスト実行は、
現在 実験的な 機能です。
詳細については、 実験的な APIs をご覧ください。
|
スクリプトのロジックが、現オペレーティングまたは現Java実行環境のバージョン、 特定のJVMシステムプロパティ、特定の環境変数にのみ依存している場合、 その目的に合った組み込みのアノテーションを使うことを考慮すべきです。 さらなる詳細については、前章をご覧ください。 |
同じスクリプトベースの条件を多数使っている場合、より速く、型安全で、 メンテナンスのしやすい方法で条件を実装するために、それに合った ExecutionCondition 拡張を書くことを考えてみてください。 |
@Test // Static JavaScript expression.
@EnabledIf("2 * 3 == 6")
void willBeExecuted() {
// ...
}
@RepeatedTest(10) // Dynamic JavaScript expression.
@DisabledIf("Math.random() < 0.314159")
void mightNotBeExecuted() {
// ...
}
@Test // Regular expression testing bound system property.
@DisabledIf("/32/.test(systemProperty.get('os.arch'))")
void disabledOn32BitArchitectures() {
assertFalse(System.getProperty("os.arch").contains("32"));
}
@Test
@EnabledIf("'CI' == systemEnvironment.get('ENV')")
void onlyOnCiServer() {
assertTrue("CI".equals(System.getenv("ENV")));
}
@Test // Multi-line script, custom engine name and custom reason.
@EnabledIf(value = {
"load('nashorn:mozilla_compat.js')",
"importPackage(java.time)",
"",
"var today = LocalDate.now()",
"var tomorrow = today.plusDays(1)",
"tomorrow.isAfter(today)"
},
engine = "nashorn",
reason = "Self-fulfilling: {result}")
void theDayAfterTomorrow() {
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);
assertTrue(tomorrow.isAfter(today));
}
スクリプトバインディング
次の名前は、各スクリプトコンテキストでバインドされているため、スクリプト内で使用可能です。
accessor は、単純な String get(String name)
メソッドを介してマップライク(map-like)な構造へのアクセスを提供します。
Name | Type | Description |
---|---|---|
|
accessor |
オペレーティングシステム環境変数のアクセサ |
|
accessor |
JVMシステムプロパティのアクセサ |
|
accessor |
設定パラメータのアクセサ |
|
|
テストまたはコンテナの表示名 |
|
|
テストまたはコンテナに振られている全てのタグ |
|
|
テストまたはコンテナのユニークなID |
3.8. タグとフィルタリング
テストクラスとメソッドは @Tag
アノテーションを用いてタグ付けできます。
それらのタグは後に テスト発見と実行 をフィルタリングするために使われます。
3.8.1. タグの構文規則
-
タグは
null
か 空 であってはならない。 -
トリミングされた タグは空白文字を含んではならない。
-
トリミングされた タグはISO制御文字を含んではならない。
-
トリミングされた タグは次の 予約語 のいずれも含んではならない。
-
,
:カンマ -
(
:左カッコ -
)
:右カッコ -
&
:アンパサンド -
|
:縦棒 -
!
:エクスクラメーション
-
上の文章で、 トリミングされた というのは、 語頭と語尾の空白文字を取り除いたということを意味します。 |
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("fast")
@Tag("model")
class TaggingDemo {
@Test
@Tag("taxes")
void testingTaxCalculation() {
}
}
3.9. テストインスタンス・ライフサイクル
各テストメソッドの独立した実行と、変化可能なテストインスタンスの状態による 予期せぬ副作用を避けるため、JUnitは各 テストメソッド を実行する前に、 各テストクラスの新しいインスタンスを生成します ( テストクラスとメソッド をご覧ください)。 この"メソッドごと"のテストインスタンス・ライフサイクルはJUnit Jupiterでは デフォルトの動作で、以前の全てのバージョンのJUnitと類似したものになっています。
@Disabled や @DisabledOnOs といった 条件 によって
無効化 された テストメソッド であっても、テストクラスはインスタンス化されることに注意してください。
これは"メソッドごと"テストインスタンス・ライフサイクルモードが有効である時でも同様です。
|
JUnit Jupiterに全テストメソッドを同じテストインスタンス上で実行してほしい場合は、
単にテストクラスに @TestInstance(Lifecycle.PER_CLASS)
アノテーションを付与するだけで
実現可能です。このモードを使用する場合、テストクラス毎に新しいテストインスタンスが一度だけ生成されます。
これによって、テストメソッドがインスタンス変数に保存された状態に依存する場合は、
@BeforeEach
または @AfterEach
メソッドでその状態をリセットする必要があるかもしれません。
”クラスごと”のモードは、デフォルトの"メソッドごと"モードに比べていくつかの追加的な利点があります。
特に、"クラスごと"モードを使うと、インターフェイスの default
メソッドと同様に、
@BeforeAll
と @AfterAll
メソッドを非静的メソッドとして宣言することが可能になります。
そのため、"クラスごと"モードでは、 @Nested
テストクラス内で @BeforeAll
と @AfterAll
メソッドを使うことができます。
Kotlinプログラミング言語でテストを書いている場合、
”クラスごと”テストインスタンス・ライフサイクルモードに切り替えることで、
@BeforeAll
と @AfterAll
メソッドの実装がより容易になるかもしれません。
3.9.1. デフォルトのテストインスタンス・ライフサイクルの変更
テストクラスまたはテストインターフェイスに @TestInstance
が付与されていない場合、
JUnit Jupiterは デフォルト のライフサイクルモードを使います。
標準的な デフォルト モードは PER_METHOD
ですが、
テスト計画全体を実行するための デフォルト を変更することが可能です。
デフォルトのテストインスタンス・ライフサイクルモードを変更するには、
単に junit.jupiter.testinstance.lifecycle.default
設定パラメータ に
TestInstance.Lifecycle
に定義されているenum定数名を(大文字・小文字を無視して)設定するだけです。
これは、JVMシステムプロパティとして渡すか、 Launcher
に渡される LauncherDiscoveryRequest
内の
設定パラメータ として渡すか、JUnit Platformの設定ファイル
(詳細については、 設定パラメータ をご覧ください。)を通して渡します。
例えば、デフォルトのテストインスタンス・ライフサイクルモードを LifeCycle.PER_CLASS
に
設定するには、JVMを次のシステムプロパティで起動してください。
-Djunit.jupiter.testinstance.lifecycle.default=per_class
しかしながら、JUnit Platformの設定ファイルを通してデフォルトの テストインスタンス・ライフサイクルモードを設定する方が、より堅牢な解決策です。 設定ファイルはプロジェクトのバージョン管理システムに取り込め、 自身のIDEやビルドソフトウェアで利用できます。
JUnit Platformの設定ファイルを通してデフォルトのテストインスタンス・ライフサイクルモードを
設定ためには、次の内容を含んだ junit-platform.properties
という名前のファイルを
クラスパス(例えば、 src/test/resources
)のルートに生成してください。
junit.jupiter.testinstance.lifecycle.default = per_class
デフォルト のテストインスタンス・ライフサイクルモードを変更することは、 一貫性を持って適用しないと、予測不可能な結果と壊れやすいビルドにつながる恐れがあります。 例えば、ビルドではデフォルトとして”クラスごと”のセマンティックを設定していながら、 IDEでのテストでは"メソッドごと"で実行していた場合、ビルドサーバで起きるエラーを デバッグすることは困難になる恐れがあります。そのため、JVMシステムプロパティの代わりに、 JUnit Platformの設定ファイルを使ってデフォルトを変更することをお薦めします。 |
3.10. ネストされたテスト
ネストされたテストは、テスト開発者が様々なグループのテスト間の関係を表現することを 可能にします。これがその美しい例です。
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.EmptyStackException;
import java.util.Stack;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, () -> stack.pop());
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, () -> stack.peek());
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
非静的なネストされたクラス(つまり、内部クラス)のみ が
@Nested テストクラスとなります。ネストは任意に深くすることができ、
それら内部クラスは一つの例外を除いて、テストクラスの完全なメンバーとして考えられます。
例外は @BeforeAll と @AfterAll で、これらは デフォルト では動作しません。
その理由は、Javaが内部クラスに static なメンバーを許さないためです。
しかしながら、この制限は @Nested テストクラスに @TestInstance(Lifecycle.PER_CLASS) を
付与することで回避できます( テストインスタンス・ライフサイクル をご覧ください)。
|
3.11. コンストラクタとメソッドへの依存性注入
JUnitの前バージョン全てにおいて、テストコンストラクタまたはメソッドは
(少なくとも標準的な Runner
実装を用いる場合は)パラメータを持つことが
許されていませんでした。JUnit Jupiterでの大きな変更の1つとして、
テストコンストラクタとメソッドどちらもパラメータを持てるようになりました。
このことは、大きな柔軟性をもたらし、コンストラクタとメソッドに 依存性の注入 が
可能になりました。
ParameterResolver
は、実行時に 動的に パラメータを解決することを
望むテスト拡張のためのAPIを定義しています。テストコンストラクタまたは @Test
、
@TestFactory
、 @BeforeEach
、 @AfterEach
、 @BeforeAll
、 @AfterAll
メソッドがパラメータを許容する場合は、そのパラメータは登録された ParameterResolver
に
よって実行時に解決されなければなりません。
現在は、3つの組み込みリゾルバが自動的に登録されます。
-
TestInfoParameterResolver
:メソッドパラメータがTestInfo
型の場合、TestInfoParameterResolver
はパラメータの値として現在のテストに応じたTestInfo
のインスタンスを供給します。TestInfo
は、テストの表示名、テストクラス、 テストメソッド、関連付けられたタグ名といった現在のテストに関する情報を集めるのに 使うことができます。表示名は、テストクラスまたはテストメソッドの名前といった技術的な名前か、@DisplayedName
で設定されたカスタム名のどちらかです。
TestInfo
は、JUnit 4の TestName
規則の代替として動作します。
次のコードは、テストコンストラクタと @BeforeEach
メソッド、 @Test
メソッドに
TestInfo
を注入させる方法を示しています。
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
@DisplayName("TestInfo Demo")
class TestInfoDemo {
TestInfoDemo(TestInfo testInfo) {
assertEquals("TestInfo Demo", testInfo.getDisplayName());
}
@BeforeEach
void init(TestInfo testInfo) {
String displayName = testInfo.getDisplayName();
assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()"));
}
@Test
@DisplayName("TEST 1")
@Tag("my-tag")
void test1(TestInfo testInfo) {
assertEquals("TEST 1", testInfo.getDisplayName());
assertTrue(testInfo.getTags().contains("my-tag"));
}
@Test
void test2() {
}
}
-
RepetitionInfoParameterResolver
:@RepeatedTest
または@BeforeEach
、@AfterEach
メソッドにおけるメソッドパラメータがRepetitionInfo`型の場合、 `RepetitionInfoParameterResolver
はRepetitionInfo
のインスタンスを供給します。RepetitionInfo
は、現在の繰り返しと対応する@RepeatedTest
の繰り返しの総数に 関する情報を集めるために利用できます。しかしながら、RepetitionInfoParameterResolver
は、@RepeatedTest
の文脈外では登録されていないことに注意してください。 繰り返しテストの例 をご覧ください。 -
TestReporterParameterResolver
:メソッドパラメータがTestReporter
型の場合、TestReporterParameterResolver
はTestReporter
のインスタンスを供給します。TestReporter
は、現在のテスト実行に関する追加情報を公開するために利用できます。 そのデータは、TestExecutionListener.reportingEntryPublished()
を通して消費され、 IDEに表示またはレポートに含まれます。JUnit Jupiterでは、JUnit 4でstdout`や `stderr
に情報を出力していた箇所にTestReporter
を使うことができます。@RunWith(JUnitPlatform.class)
を使うと、全てのレポートされたエントリをstdout
に出力します。
import java.util.HashMap;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestReporter;
class TestReporterDemo {
@Test
void reportSingleValue(TestReporter testReporter) {
testReporter.publishEntry("a key", "a value");
}
@Test
void reportSeveralValues(TestReporter testReporter) {
HashMap<String, String> values = new HashMap<>();
values.put("user name", "dk38");
values.put("award year", "1974");
testReporter.publishEntry(values);
}
}
他のパラメータリゾルバは、 @ExtendWith を用いた適切な 拡張 を
登録することによって明示的に有効化する必要があります。
|
カスタム ParameterResolver
の例に関して RandomParametersExtension
を確認しましょう。
リリース可能なものではありませんが、拡張モデルとパラメータ解決プロセス両方の単純さと表現性を
例示しています。 MyRandomParametersTest
は、 @Test
メソッドへのランダム値の
挿入方法をを示しています。
@ExtendWith(RandomParametersExtension.class)
class MyRandomParametersTest {
@Test
void injectsInteger(@Random int i, @Random int j) {
assertNotEquals(i, j);
}
@Test
void injectsDouble(@Random double d) {
assertEquals(0.0, d, 1.0);
}
}
現実的なユースケースとして、 MockitoExtension
と
SpringExtension
のソースコードを確認してください。
3.12. テストインターフェイスとデフォルトメソッド
JUnit Jupiterは、 @Test
と @RepeatedTest
、 @ParameterizedTest
、
@TestFactory
、 @TestTemplate
、 @BeforeEach
、 @AfterEach
に
インターフェイスの default
メソッドを宣言できるようにしています。
@BeforeAll
と @AfrterAll
はテストインターフェイス内で static
メソッドを
宣言するか、 もし テストインターフェイスまたはテストクラスに
@TestInstance(Lifecycle.PER_CLASS)
が付与されている場合は
インターフェイス default
メソッドを宣言することができます
( テストインスタンス・ライフサイクル をご覧ください)。
いくつかの例を示します。
@TestInstance(Lifecycle.PER_CLASS)
interface TestLifecycleLogger {
static final Logger LOG = Logger.getLogger(TestLifecycleLogger.class.getName());
@BeforeAll
default void beforeAllTests() {
LOG.info("Before all tests");
}
@AfterAll
default void afterAllTests() {
LOG.info("After all tests");
}
@BeforeEach
default void beforeEachTest(TestInfo testInfo) {
LOG.info(() -> String.format("About to execute [%s]",
testInfo.getDisplayName()));
}
@AfterEach
default void afterEachTest(TestInfo testInfo) {
LOG.info(() -> String.format("Finished executing [%s]",
testInfo.getDisplayName()));
}
}
interface TestInterfaceDynamicTestsDemo {
@TestFactory
default Collection<DynamicTest> dynamicTestsFromCollection() {
return Arrays.asList(
dynamicTest("1st dynamic test in test interface", () -> assertTrue(true)),
dynamicTest("2nd dynamic test in test interface", () -> assertEquals(4, 2 * 2))
);
}
}
@ExtenWith`と `@Tag
はテストインターフェイスとして宣言することができるため、
インターフェイスを実装したクラスは自動的にタグと拡張を継承します。
TimingExtension のソースコードを見るには、
BeforeとAfterのテスト実行コールバック をご覧ください。
@Tag("timed")
@ExtendWith(TimingExtension.class)
interface TimeExecutionLogger {
}
テストクラスでは、これらのテストインターフェイスを実装することで適用することができます。
class TestInterfaceDemo implements TestLifecycleLogger,
TimeExecutionLogger, TestInterfaceDynamicTestsDemo {
@Test
void isEqualValue() {
assertEquals(1, 1, "is always equal");
}
}
TestInterfaceDemo
を実行すると、次と同様の出力が得られます。
:junitPlatformTest
INFO example.TestLifecycleLogger - Before all tests
INFO example.TestLifecycleLogger - About to execute [dynamicTestsFromCollection()]
INFO example.TimingExtension - Method [dynamicTestsFromCollection] took 13 ms.
INFO example.TestLifecycleLogger - Finished executing [dynamicTestsFromCollection()]
INFO example.TestLifecycleLogger - About to execute [isEqualValue()]
INFO example.TimingExtension - Method [isEqualValue] took 1 ms.
INFO example.TestLifecycleLogger - Finished executing [isEqualValue()]
INFO example.TestLifecycleLogger - After all tests
Test run finished after 190 ms
[ 3 containers found ]
[ 0 containers skipped ]
[ 3 containers started ]
[ 0 containers aborted ]
[ 3 containers successful ]
[ 0 containers failed ]
[ 3 tests found ]
[ 0 tests skipped ]
[ 3 tests started ]
[ 0 tests aborted ]
[ 3 tests successful ]
[ 0 tests failed ]
BUILD SUCCESSFUL
この機能の他のあり得る適用としては、インターフェイス契約のためにテストを書くことです。
例えば、 Object.equals
または Comparable.compareTo
の実装が
どう振る舞うべきかのテストを、次のように書くことができます。
public interface Testable<T> {
T createValue();
}
public interface EqualsContract<T> extends Testable<T> {
T createNotEqualValue();
@Test
default void valueEqualsItself() {
T value = createValue();
assertEquals(value, value);
}
@Test
default void valueDoesNotEqualNull() {
T value = createValue();
assertFalse(value.equals(null));
}
@Test
default void valueDoesNotEqualDifferentValue() {
T value = createValue();
T differentValue = createNotEqualValue();
assertNotEquals(value, differentValue);
assertNotEquals(differentValue, value);
}
}
public interface ComparableContract<T extends Comparable<T>> extends Testable<T> {
T createSmallerValue();
@Test
default void returnsZeroWhenComparedToItself() {
T value = createValue();
assertEquals(0, value.compareTo(value));
}
@Test
default void returnsPositiveNumberComparedToSmallerValue() {
T value = createValue();
T smallerValue = createSmallerValue();
assertTrue(value.compareTo(smallerValue) > 0);
}
@Test
default void returnsNegativeNumberComparedToSmallerValue() {
T value = createValue();
T smallerValue = createSmallerValue();
assertTrue(smallerValue.compareTo(value) < 0);
}
}
テストクラスでは、2つの契約インターフェイスを実装することで、対応するテストを継承します。 もちろん、抽象メソッドを実装する必要があります。
class StringTests implements ComparableContract<String>, EqualsContract<String> {
@Override
public String createValue() {
return "foo";
}
@Override
public String createSmallerValue() {
return "bar"; // 'b' < 'f' in "foo"
}
@Override
public String createNotEqualValue() {
return "baz";
}
}
上記のテストは、単なる例であって、完全ではありません。 |
3.13. 繰り返しテスト
JUnit Jupiterは、 @RepeatedTest
を付与し、繰り返してほしい回数を設定するだけで、
特定回数テストを繰り返す機能を提供しています。
繰り返しテストの各呼び出しは、通常の @Test
メソッドの実行のように振る舞い、
同じライフサイクル・コールバックと拡張を完全にサポートしています。
次の例は、自動で10回繰り返す repeatedTest()
という名前のテストの宣言方法を示しています。
@RepeatedTest(10)
void repeatedTest() {
// ...
}
繰り返し回数の設定に加えて、 @RepeatedTest
アノテーションの
name
属性を用いることでカスタム表示名も設定できます。さらに、表示名は、
静的なテキストと動的なプレースホルダの組み合わせで構成されるパターンにすることもできます。
次のプレースホルダが現在サポートされています。
-
{displayName}
:@RepeatedTest
メソッドの表示名 -
{currentRepetition}
: 現在の繰り返し回数 -
{totalRepetition}
: 繰り返し回数の合計
ある繰り返し回数時点でのデフォルトの表示名は、次のパターンに基づいて生成されます:
'repetition {currentRepetition} of {totalRepetitions}'
。
そのため、先ほどの例の各繰り返し回数における表示名は次のようになります:
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
などとなります。
現在の繰り返し回数と繰り返しの合計数の情報をプログラム的に集めるために、
@RepeatedTest
または @BeforeEach
、 @AfterEach
に
RepetitionInfo
インスタンスを挿入することができます。
3.13.1. 繰り返しテストの例
この章の最後にある RepeatedTestsDemo
クラスは、繰り返しテストの
いくつかの例を示しています。
repeatedTest()
メソッドは、前章からの例です。
一方、 repeatedTestWithRepetitionInfo()
は、現在繰り返されているテストの
繰り返し合計数を得るために RepetitionInfo
インスタンスをテストに注入する方法を
示しています。
その次の2つのメソッドは、 @RepeatedTest
のカスタム @DisplayName
を
各繰り返しの表示名内に含ませる方法を示しています。 customDisplayName()
は
カスタム表示名とカスタムパターンを組み合わせており、 TestInfo
を使って生成された
表示名のフォーマットを検証しています。 Repeat!
は @DisplayName
宣言から来る
{displayName}
で、 1/1
は {currentRepetition}/{totalRepetitions}
から来ています。
対照的に、 customDisplayNameWithLongPattern()
は、先ほど説明した事前定義の
RepeatedTest.LONG_DISPLAY_NAME
パターンを使っています。
repeatedTestInGerman()
は、繰り返しテストの表示名を他国言語(この場合はドイツ語です)に
翻訳する機能を示しています。その結果、各繰り返しにおける名前は、 Wiederholung 1 von 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 1 for customDisplayName
INFO: About to execute repetition 1 of 1 for customDisplayNameWithLongPattern
INFO: About to execute repetition 1 of 5 for repeatedTestInGerman
INFO: About to execute repetition 2 of 5 for repeatedTestInGerman
INFO: About to execute repetition 3 of 5 for repeatedTestInGerman
INFO: About to execute repetition 4 of 5 for repeatedTestInGerman
INFO: About to execute repetition 5 of 5 for repeatedTestInGerman
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.logging.Logger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.TestInfo;
class RepeatedTestsDemo {
private Logger logger = // ...
@BeforeEach
void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) {
int currentRepetition = repetitionInfo.getCurrentRepetition();
int totalRepetitions = repetitionInfo.getTotalRepetitions();
String methodName = testInfo.getTestMethod().get().getName();
logger.info(String.format("About to execute repetition %d of %d for %s", //
currentRepetition, totalRepetitions, methodName));
}
@RepeatedTest(10)
void repeatedTest() {
// ...
}
@RepeatedTest(5)
void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) {
assertEquals(5, repetitionInfo.getTotalRepetitions());
}
@RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}")
@DisplayName("Repeat!")
void customDisplayName(TestInfo testInfo) {
assertEquals(testInfo.getDisplayName(), "Repeat! 1/1");
}
@RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME)
@DisplayName("Details...")
void customDisplayNameWithLongPattern(TestInfo testInfo) {
assertEquals(testInfo.getDisplayName(), "Details... :: repetition 1 of 1");
}
@RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}")
void repeatedTestInGerman() {
// ...
}
}
ConsoleLauncher
またはunicodeテーマを有効化した junitPlatformTest
Gradleプラグインを
使うと、 RepeatedTestsDemo
の実行結果は次のようなコンソール出力を行います。
├─ RepeatedTestsDemo ✔
│ ├─ repeatedTest() ✔
│ │ ├─ repetition 1 of 10 ✔
│ │ ├─ repetition 2 of 10 ✔
│ │ ├─ repetition 3 of 10 ✔
│ │ ├─ repetition 4 of 10 ✔
│ │ ├─ repetition 5 of 10 ✔
│ │ ├─ repetition 6 of 10 ✔
│ │ ├─ repetition 7 of 10 ✔
│ │ ├─ repetition 8 of 10 ✔
│ │ ├─ repetition 9 of 10 ✔
│ │ └─ repetition 10 of 10 ✔
│ ├─ repeatedTestWithRepetitionInfo(RepetitionInfo) ✔
│ │ ├─ repetition 1 of 5 ✔
│ │ ├─ repetition 2 of 5 ✔
│ │ ├─ repetition 3 of 5 ✔
│ │ ├─ repetition 4 of 5 ✔
│ │ └─ repetition 5 of 5 ✔
│ ├─ Repeat! ✔
│ │ └─ Repeat! 1/1 ✔
│ ├─ Details... ✔
│ │ └─ Details... :: repetition 1 of 1 ✔
│ └─ repeatedTestInGerman() ✔
│ ├─ Wiederholung 1 von 5 ✔
│ ├─ Wiederholung 2 von 5 ✔
│ ├─ Wiederholung 3 von 5 ✔
│ ├─ Wiederholung 4 von 5 ✔
│ └─ Wiederholung 5 von 5 ✔
3.14. パラメータ化テスト
パラメータ化テストを使うと、テストを異なる引数で複数回実行できるようになります。
パラメータ化テストは、通常の @Test
メソッドの代わりに @ParameterizedTest
アノテーションを付与するだけで宣言することができます。
さらに、各呼び出して供給されテストで 消費される 引数として、
少なくとも1つの source を宣言する必要があります。
次の例は、パラメータ化テストを示していて、 @ValueSource
アノテーションを使って
引数のソースとして String
配列を指定しています。
@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
assertTrue(isPalindrome(candidate));
}
上記のパラメータ化テストメソッドを実行すると、各呼び出しは別々にレポートされます。
例えば、 ConsoleLauncher
は次のようなものを出力します。
palindromes(String) ✔
├─ [1] racecar ✔
├─ [2] radar ✔
└─ [3] able was I ere I saw elba ✔
パラメータ化テストは、現在 実験的な 機能です。 詳細については、 実験的な APIs をご覧ください。 |
3.14.1. 必要なセットアップ
パラメータ化テストを使うためには、 junit-jupiter-params
アーティファクトを
依存関係に加える必要があります。詳細については、 依存関係のメタデータ をご覧ください。
3.15. 引数の消費
パラメータ化テストメソッドは典型的に、設定されたソース
( 引数のソース をご覧ください。)
から直接、引数を 消費 します。引数ソースとメソッドパラメータのインデックスは
1対1の相関関係に従います( @CsvSource
の例をご覧ください)。
しかしながら、パラメータ化テストメソッドは、ソースから得た引数をひとつのオブジェクトに
集約 して、メソッドに渡すこともできます( 引数集約 をご覧ください)。
追加的な引数もまた(例えば、 TestInfo
や TestReporter
などのインスタンスを
獲得するために) ParameterResolver
によって提供されます。
特に、パラメータ化テストメソッドは、次のルールに従って形式的なパラメータを宣言する必要があります。
-
まず、0個以上の インデックスされた引数 を宣言する。
-
次に、0個以上の アグリゲータ を宣言する。
-
最後に、0個以上の
ParameterResolver
によって供給される引数を宣言する。
この文脈で、 インデックスされた引数 とは、 ArgumentsProvider
によって提供される
Arguments
内で与えられたインデックスに対応する引数です。
ArgumentsProvider
は、パラメータ化メソッドが保持する形式的なパラメータリストにおいて
同じインデックスにあるメソッドに引数として渡されます。 アグリゲータ は、
ArgumentsAccessor 型または @AggregateWith の付与されたパラメータです。
3.15.1. 引数のソース
すぐに使えるように、JUnit Jupiterは非常に多くの ソース アノテーションを提供しています。
次の各章はそれぞれ、簡潔な概要とそれぞれの例を提供しています。
さらなる情報に関しては、 org.junit.jupiter.params.provider
パッケージのJavaDocを参照してください。
@ValueSource
@ValueSource
は最も単純なソースの1つです。
リテラル値の配列を1つ設定することができ、パラメータ化テスト呼び出しにつき、
1つのパラメータを提供できます。
次のリテラル値の型が @ValueSource
にサポートされています。
-
short
-
byte
-
int
-
long
-
float
-
double
-
char
-
java.lang.String
-
java.lang.Class
例えば、次の @ParameterizedTest
メソッドはそれぞれ 1
、 2
、 3
の値とともに
3回呼び出されます。
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
assertTrue(argument > 0 && argument < 4);
}
@EnumSource
@EnumSource
は、 Enum
定数に対して便利な機能を提供します。
このアノテーションは、使われる定数を特定するために、オプションで names
パラメータを
提供します。省略する場合は、次の例のように全ての定数が使われます。
@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithEnumSource(TimeUnit timeUnit) {
assertNotNull(timeUnit);
}
@ParameterizedTest
@EnumSource(value = TimeUnit.class, names = { "DAYS", "HOURS" })
void testWithEnumSourceInclude(TimeUnit timeUnit) {
assertTrue(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));
}
@EnumSource
アノテーションはまた、テストメソッドに渡すパラメータを細かく制御するために、
オプションで mode
パラメータを提供します。例えば、次の例では、enum定数プールから
namesを取り除いたり、正規表現を設定しています。
@ParameterizedTest
@EnumSource(value = TimeUnit.class, mode = EXCLUDE, names = { "DAYS", "HOURS" })
void testWithEnumSourceExclude(TimeUnit timeUnit) {
assertFalse(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));
assertTrue(timeUnit.name().length() > 5);
}
@ParameterizedTest
@EnumSource(value = TimeUnit.class, mode = MATCH_ALL, names = "^(M|N).+SECONDS$")
void testWithEnumSourceRegex(TimeUnit timeUnit) {
String name = timeUnit.name();
assertTrue(name.startsWith("M") || name.startsWith("N"));
assertTrue(name.endsWith("SECONDS"));
}
@MethodSource
@MethodSource
では、テストクラスまたは外部クラスの ファクトリー メソッドを
1つ以上使うことができます。ファクトリーメソッドは、 Stream
または Iterable
、
Iterator
、引数の配列を返す必要があります。さらに、ファクトリーメソッドには
引数を与えられません。テストクラス内のファクトリーメソッドは、テストクラスに
@TestInstance(Lifecycle.PER_CLASS)
が付与されていない限り、
static
である必要があります。一方、外部クラスのファクトリーメソッドは
常に static
である必要があります。
パラメータが1つだけ必要な場合は、次の例が示しているように、パラメータの型のインスタンスの`Stream`を返すことができます。
@ParameterizedTest
@MethodSource("stringProvider")
void testWithSimpleMethodSource(String argument) {
assertNotNull(argument);
}
static Stream<String> stringProvider() {
return Stream.of("foo", "bar");
}
@MethodSource
を通して明示的にファクトリーメソッドの名前を提供しない場合、
JUnit Jupiterは、慣例にならって現在の @ParameterizedTest
と
同じ名前を持つ ファクトリー メソッドを探します。これを次の例で示します。
@ParameterizedTest
@MethodSource
void testWithSimpleMethodSourceHavingNoValue(String argument) {
assertNotNull(argument);
}
static Stream<String> testWithSimpleMethodSourceHavingNoValue() {
return Stream.of("foo", "bar");
}
DoubleStream
や IntStream
、 LongStream
といったプリミティブ型の
Streamもまた、次の例のようにサポートされています。
@ParameterizedTest
@MethodSource("range")
void testWithRangeMethodSource(int argument) {
assertNotEquals(9, argument);
}
static IntStream range() {
return IntStream.range(0, 20).skip(10);
}
テストメソッドが複数のパラメータを宣言している場合、
下に示すように Arguments
インスタンスのコレクションまたはストリームを
返す必要があります。 Arguments.of(Object…)
は、
Arguments
インターフェイスで定義されている静的なファクトリーメソッドです。
@ParameterizedTest
@MethodSource("stringIntAndListProvider")
void testWithMultiArgMethodSource(String str, int num, List<String> list) {
assertEquals(3, str.length());
assertTrue(num >=1 && num <=2);
assertEquals(2, list.size());
}
static Stream<Arguments> stringIntAndListProvider() {
return Stream.of(
Arguments.of("foo", 1, Arrays.asList("a", "b")),
Arguments.of("bar", 2, Arrays.asList("x", "y"))
);
}
外部の static
ファクトリー メソッドは、
次の例で示すように 完全修飾メソッド名 によって参照されます。
package example;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
class ExternalMethodSourceDemo {
@ParameterizedTest
@MethodSource("example.StringsProviders#blankStrings")
void testWithExternalMethodSource(String blankString) {
// test with blank string
}
}
class StringsProviders {
static Stream<String> blankStrings() {
return Stream.of("", " ", " \n ");
}
}
@CsvSource
@CsvSource
は、引数リストをコンマ区切りの値(つまり、 String
リテラル)として
表現できるようにします。
@ParameterizedTest
@CsvSource({ "foo, 1", "bar, 2", "'baz, qux', 3" })
void testWithCsvSource(String first, int second) {
assertNotNull(first);
assertNotEquals(0, second);
}
@CsvSource
は、シングルクォーテーション '
を引用文字として使います。
上の例と下の表の 'baz, qux'
の値をご覧ください。
引用された空の値 ''
は、空の String
となります。
一方、完全に 空 の値は null
参照として解釈されます。
null
参照が対象とする型がプリミティブ型の場合、
ArgumentConversionException
が投げられます。
入力例 | 引数リストの結果 |
---|---|
|
|
|
|
|
|
|
|
@CsvFileSource
@CsvFileSource
は、CSVファイルをクラスパスから使えるようにします。
パラメータ化テストが1回呼び出される度に、CSVファイルの各行が読み込まれます。
@ParameterizedTest
@CsvFileSource(resources = "two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSource(String first, int second) {
assertNotNull(first);
assertNotEquals(0, second);
}
Country, reference
Sweden, 1
Poland, 2
"United States of America", 3
@CsvSource で使われている構文とは対照的に、
@CsvFileSource では引用文字としてダブルクォーテーション " を使います。
上記の例の "United States of America" をご覧ください。
引用された空の値 ”” は、空の String となります。
一方、完全に 空 の値は null 参照として解釈されます。
null 参照が対象とする型がプリミティブ型の場合、 ArgumentConversionException が
投げられます。
|
@ArgumentSource
@ArgumentSource
はカスタムの再利用可能な ArgumentsProvider
を
特定するために使うことができます。
@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void testWithArgumentsSource(String argument) {
assertNotNull(argument);
}
public class MyArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of("foo", "bar").map(Arguments::of);
}
}
3.15.2. 引数変換
拡大的な変換
JUnit Jupiterは @ParamterizedTest
に供給する引数のために、
拡大的なプリミティブ変換を
サポートしています。例えば、 @ValueSource(ints = { 1, 2, 3 })
が付与された
パラメータ化テストは、 int
型のみならず、 long
や float
、 double
型の
引数も受けることができます。
暗示的な変換
@CsvSource
のようなユースケースをサポートするために、
JUnit Jupiterは組み込みの暗示的な型変換をいくつか提供しています。
変換プロセスは、各メソッドパラメータの宣言された型に依存します。
例えば、 @ParameterizedTest
が TimeUnit
型のパラメータを宣言していて、
ソースから供給された実際の型が String
であった場合、 String
は
自動的に対応する TimeUnit
enum定数に変換されます。
@ParameterizedTest
@ValueSource(strings = "SECONDS")
void testWithImplicitArgumentConversion(TimeUnit argument) {
assertNotNull(argument.name());
}
String
インスタンスは現在、次の対象型に暗示的に変換されます。
対象型 | 例 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
`Enum`サブクラス |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
StringからObjectへの予備的な変換
Stringから上に列挙されている対象型への暗示的な変換に加えて、 JUnit Jupiterでは、対象型が下の定義に合致した ファクトリーメソッド または ファクトリーコンストラクタ を宣言する場合、 `String`をその対象型へ 自動変換する予備的な機構を提供します。
-
ファクトリーメソッド :対象型で宣言されている非プライベートかつ
static
なメソッドで、 1つのString
引数を取り、対象型のインスタンスを返すもの。 メソッド名は任意であり、特定の慣習にも従う必要はありません。 -
ファクトリーコンストラクタ :対象型のプライベートでないコンストラクタで、 1つの `String`引数を取るもの。
複数の ファクトリーメソッド が見つかった場合、それらは無視されます。 ファクトリーメソッド と ファクトリーコンストラクタ が見つかった場合、 ファクトリーメソッドがコンストラクタの代わりに使われます。 |
例えば、次の @ParameterizedTest
メソッドの中で、 Book
引数は
Book.fromTitle(String)
ファクトリーメソッドが呼び出されることで生成され、
"42 Cats"
が本のタイトルとして渡されます。
@ParameterizedTest
@ValueSource(strings = "42 Cats")
void testWithImplicitFallbackArgumentConversion(Book book) {
assertEquals("42 Cats", book.getTitle());
}
public class Book {
private final String title;
private Book(String title) {
this.title = title;
}
public static Book fromTitle(String title) {
return new Book(title);
}
public String getTitle() {
return this.title;
}
}
明示的な変換
暗黙的な引数変換の代わりに、次の例のように @ConvertWith
アノテーションを使うことで、
あるパラメータに対して明示的に ArgumentConverter
を特定することができます。
@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithExplicitArgumentConversion(
@ConvertWith(ToStringArgumentConverter.class) String argument) {
assertNotNull(TimeUnit.valueOf(argument));
}
public class ToStringArgumentConverter extends SimpleArgumentConverter {
@Override
protected Object convert(Object source, Class<?> targetType) {
assertEquals(String.class, targetType, "Can only convert to String");
return String.valueOf(source);
}
}
明示的な引数変換は、テストと拡張の開発者によって実装される必要があります。
そのため、 junit-jupiter-params
では、参照実装として使える明示的な引数変換器:
JavaTimeArgumentConverter
を提供しています。
合成アノテーションである JavaTimeConversionPattern
を通して使うことができます。
@ParameterizedTest
@ValueSource(strings = { "01.01.2017", "31.12.2017" })
void testWithExplicitJavaTimeConverter(
@JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) {
assertEquals(2017, argument.getYear());
}
3.15.3. 引数集約
デフォルトでは、 @ParameterizedTest
メソッドに渡される各 引数
は、
1つのメソッドパラメータに対応しています。その結果として、大量の引数を供給することが
期待される引数ソースは、巨大なメソッドシグネチャになる可能性があります。
そのような場合、 ArgumentsAccessor
を複数のパラメータの代わりに使うことができます。
このAPIを使うことで、テストメソッドに渡された1つのパラメータを通して提供された
引数にアクセスすることができます。さらに、 暗黙的な変換
で議論している型変換もサポートしています。
@ParameterizedTest
@CsvSource({
"Jane, Doe, F, 1990-05-20",
"John, Doe, M, 1990-10-22"
})
void testWithArgumentsAccessor(ArgumentsAccessor arguments) {
Person person = new Person(arguments.getString(0),
arguments.getString(1),
arguments.get(2, Gender.class),
arguments.get(3, LocalDate.class));
if (person.getFirstName().equals("Jane")) {
assertEquals(Gender.F, person.getGender());
}
else {
assertEquals(Gender.M, person.getGender());
}
assertEquals("Doe", person.getLastName());
assertEquals(1990, person.getDateOfBirth().getYear());
}
ArgumentsAccessor
のインスタンス は、 ArgumentsAccessor
型のいかなるパラメータにも自動的に挿入されます。
カスタムアグリゲータ
ArgumentsAccessor
を用いた @ParameterizedTest
メソッドの引数への
直接アクセスとは別に、JUnit Jupiterはカスタムで再利用可能な アグリゲータ の使用も
サポートしています。
カスタムアグリゲータを使うためには、単に ArgumentsAggregator`インターフェイスを実装し、
`@ParameterizedTest
メソッド内で互換可能なパラメータに対して
@AggregateWith
アノテーションを付与して登録するだけです。
集約の結果は、パラメータ化テストが呼び出された時に、対応するパラメータへの引数として
提供されます。
@ParameterizedTest
@CsvSource({
"Jane, Doe, F, 1990-05-20",
"John, Doe, M, 1990-10-22"
})
void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) {
// perform assertions against person
}
public class PersonAggregator implements ArgumentsAggregator {
@Override
public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) {
return new Person(arguments.getString(0),
arguments.getString(1),
arguments.get(2, Gender.class),
arguments.get(3, LocalDate.class));
}
}
コードベースにまたがって複数のパラメータ化テストに対して繰り返し
@AggregateWith(MyTypeAggregator.class)
を宣言している場合、
@AggregateWith(MyTypeAggregator.class)
のメタアノテーションとして
@CsvToMyType
のようなカスタム 合成アノテーション を作成できます。
次の例は、カスタム @CsvToPerson
アノテーションを用いた動作例を示しています。
@ParameterizedTest
@CsvSource({
"Jane, Doe, F, 1990-05-20",
"John, Doe, M, 1990-10-22"
})
void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) {
// perform assertions against person
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AggregateWith(PersonAggregator.class)
public @interface CsvToPerson {
}
3.15.4. 表示名のカスタマイズ
デフォルトでは、パラメータ化テスト呼び出しの表示名は、
呼び出しインデックスと特定の呼び出しに対する全ての引数の String
表現を含んでいます。
しかしながら、次の例のように @ParameterizedTest
アノテーションの
name
属性によって呼び出し表示名をカスタマイズできます。
@DisplayName("Display name of container")
@ParameterizedTest(name = "{index} ==> first=''{0}'', second={1}")
@CsvSource({ "foo, 1", "bar, 2", "'baz, qux', 3" })
void testWithCustomDisplayNames(String first, int second) {
}
上記のメソッドを ConsoleLauncher
を使って実行すると、次のような出力が表示されます。
Display name of container ✔
├─ 1 ==> first='foo', second=1 ✔
├─ 2 ==> first='bar', second=2 ✔
└─ 3 ==> first='baz, qux', second=3 ✔
カスタム表示名では、次のプレースホルダがサポートされています。
プレースホルダ | 説明 |
---|---|
{index} |
現在の呼び出しインデックス(1始まり) |
{arguments} |
完全な引数リスト(CSV形式) |
{0}, {1}, … |
各引数 |
3.15.5. ライフサイクルと相互運用性
パラメータ化テストの各呼び出しは、通常の @Test
メソッドと
同じライフサイクルを持っています。例えば、各呼び出し前には
@BeforeEach
メソッドが実行されます。
動的テスト と同じように、
呼び出しはIDEのテストツリーでは一つ一つ表れます。同一のテストクラスに、
自由に @Test
と @ParameterizedTest
を混ぜることができます。
@ParameterizedTest
メソッドと合わせて ParameterResolver
拡張を使うことができます。
しかしながら、引数ソースによって解決されたパラメータは、
引数リストの最初に来る必要があります。
テストクラスは様々なパラメータリストを持つパラメータ化テストと同様に
通常のテストを含むこともあるので、引数ソースからの値は @BeforeEach
といった
ライフサイクルメソッドやテストクラスコンストラクタは解決されません。
@BeforeEach
void beforeEach(TestInfo testInfo) {
// ...
}
@ParameterizedTest
@ValueSource(strings = "foo")
void testWithRegularParameterResolver(String argument, TestReporter testReporter) {
testReporter.publishEntry("argument", argument);
}
@AfterEach
void afterEach(TestInfo testInfo) {
// ...
}
3.16. テストテンプレート
@TestTemplate
メソッドは、通常のテストケースではなく、
むしろテストケースのためのテンプレートです。
したがって、 @TestTemplate
は、登録されたプロバイダによって返される
呼び出し文脈の数に応じて複数回呼び出されるものとして設計されています。
そのため、登録された TestTemplateInvocationContextProvider
拡張と併せて使われる必要があります。
テストテンプレートメソッドの各呼び出しは、通常の @Test
メソッドの実行と
同じように振る舞い、同じライフサイクルのコールバックと拡張が完全にサポートされています。
用法例については、 テストテンプレートに対する呼び出し文脈の提供 を参照ください。
3.17. 動的テスト
アノテーション で説明したJUnit Jupiterの
標準的な @Test
アノテーションは、JUnit 4の @Test
アノテーションに非常に似通っています。
どちらもテストケースを実装したメソッドです。これらのテストケースはコンパイル時に
完全に決定するという意味では静的であり、それらの振る舞いは実行時に変更することはできません。
アサンプションは、意図的にかなり表現性に制限のあるものですが、動的な振る舞いの基本的な形式を提供します 。
これらの標準的なテストに加えて、全く新しい種類のテストプログラミングモデルが
JUnit Jupiterでは導入されました。
この新しいテストとは、 動的テスト です。
動的テストは、 @TestFactory
が付与されたファクトリーメソッドによって、
実行時に生成されます。
@Test
メソッドとは対照的に、 @TestFactory
メソッド自身はテストケースではなく、
むしろテストケースのためのファクトリーです。
そのため、動的テストはファクトリーの産出物となります。
技術的なことを言うと、 @TestFactory
メソッドは、 DynamicNode
インスタンスの
Stream
または Collection
、 Iterable
、 Iterator
を返さなければなりません。
DynamicNode
のインスタンス化可能なサブクラスは DynamicContainer
と DynamicTest
です。
DynamicContainer
インスタンスは、 表示名 と動的な子ノードのリストで構成されており、
動的なノードの任意なネスト階層を生成できます。
DynamicTest
インスタンスは、遅延実行され、テストケースの動的で非決定的な生成が
可能となります。
@TestFactory
によって返される Stream
はいずれも、 stream.close()
を
呼ぶことで適切に閉じられます。これによって、 Files.lines()
のような資源を
安全に使うことができます。
@Test
メソッドと同様に、 @TestFactory
メソッドは private
または static
は不可で、
ParameterResolvers
で解決されるパラメータをオプションで宣言できます。
DynamicTest
は実行時に生成されるテストケースで、 表示名 と Executable
で
構成されています。 Executable
は @FunctionalInterface
で、
このインターフェイスは ラムダ表現 または メソッド参照 として提供されることができる
動的テストの実装であることを意味します。
動的テストのライフサイクル
動的テストの実行ライフサイクルは、標準的な @Test ケースとは全く異なります。
特に、各動的テストに対してのライフサイクルのコールバックはありません。
このことは、 @BeforeEach と @AfterEach 、それに対応した拡張コールバックは
@TestFactory メソッドに対して実行され、各 動的テスト には実行されないことを意味します。
つまり、動的テストに対してラムダ表現でテストインスタンスからフィールドにアクセスしても、
それらのフィールドは、同じ @TestFactory メソッドで生成された
個々の動的テストの実行中は、コールバックメソッドやその拡張によってリセットされません。
|
JUnit Jupiter 5.2.0 時点では、 動的テストは常にファクトリーメソッドによって生成される必要があります。 しかしながら、これは後のリリースにある登録機能によって補完されるかもしれません。
動的テストは現在 実験的な 機能です。 詳細に関しては、 実験的な APIs をご覧ください。 |
3.17.1. 動的テストの例
次の DynamicTestsDemo
クラスは、
テストファクトリーと動的テストのいくつかの例を示しています。
最初のメソッドは不正な型を返しています。
不正な返り値の型はコンパイル時に検出することができないため、
実行時に検出され JUnitException
が投げられます。
次の5つのメソッドは、 DynamicTest
インスタンスの Collection
または Iterable
、
Iterator
、 Stream
を生成する非常に単純な例です。
これらの例のほとんどは実際には動的振る舞いを示しておらず、
単に原則的にサポートされている返却型を示しています。
しかしながら、 dynamicTestsFromStream()
と dynamicTestsFromIntStream()
は、
String
のセットや入力値の範囲に対する動的テストの生成がいかに簡単かを示しています。
次のメソッドは、性質上、真に動的なものです。
generateRandomNumberOfTests()
、ランダム数を生成する Iterator
と表示名生成器、
テスト実行器を実装しており、その3つを DynamicTest.stream()
に提供しています。
generateRandomNumberOfTests()
の非決定的な振る舞いは、
もちろんテスト反復可能性に抵触しており、注意深く取り扱われるべきではありますが、
動的テストの表現性と能力を示しています。
最後のメソッドは、 DynamicContainer
を使って動的テストのネスト階層を生成しています。
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.ThrowingConsumer;
class DynamicTestsDemo {
// This will result in a JUnitException!
@TestFactory
List<String> dynamicTestsWithInvalidReturnType() {
return Arrays.asList("Hello");
}
@TestFactory
Collection<DynamicTest> dynamicTestsFromCollection() {
return Arrays.asList(
dynamicTest("1st dynamic test", () -> assertTrue(true)),
dynamicTest("2nd dynamic test", () -> assertEquals(4, 2 * 2))
);
}
@TestFactory
Iterable<DynamicTest> dynamicTestsFromIterable() {
return Arrays.asList(
dynamicTest("3rd dynamic test", () -> assertTrue(true)),
dynamicTest("4th dynamic test", () -> assertEquals(4, 2 * 2))
);
}
@TestFactory
Iterator<DynamicTest> dynamicTestsFromIterator() {
return Arrays.asList(
dynamicTest("5th dynamic test", () -> assertTrue(true)),
dynamicTest("6th dynamic test", () -> assertEquals(4, 2 * 2))
).iterator();
}
@TestFactory
Stream<DynamicTest> dynamicTestsFromStream() {
return Stream.of("A", "B", "C")
.map(str -> dynamicTest("test" + str, () -> { /* ... */ }));
}
@TestFactory
Stream<DynamicTest> dynamicTestsFromIntStream() {
// Generates tests for the first 10 even integers.
return IntStream.iterate(0, n -> n + 2).limit(10)
.mapToObj(n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0)));
}
@TestFactory
Stream<DynamicTest> generateRandomNumberOfTests() {
// Generates random positive integers between 0 and 100 until
// a number evenly divisible by 7 is encountered.
Iterator<Integer> inputGenerator = new Iterator<Integer>() {
Random random = new Random();
int current;
@Override
public boolean hasNext() {
current = random.nextInt(100);
return current % 7 != 0;
}
@Override
public Integer next() {
return current;
}
};
// Generates display names like: input:5, input:37, input:85, etc.
Function<Integer, String> displayNameGenerator = (input) -> "input:" + input;
// Executes tests based on the current input value.
ThrowingConsumer<Integer> testExecutor = (input) -> assertTrue(input % 7 != 0);
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor);
}
@TestFactory
Stream<DynamicNode> dynamicTestsWithContainers() {
return Stream.of("A", "B", "C")
.map(input -> dynamicContainer("Container " + input, Stream.of(
dynamicTest("not null", () -> assertNotNull(input)),
dynamicContainer("properties", Stream.of(
dynamicTest("length > 0", () -> assertTrue(input.length() > 0)),
dynamicTest("not empty", () -> assertFalse(input.isEmpty()))
))
)));
}
}
4. テストの実行
4.1. IDEサポート
4.1.1. IntelliJ IDEA
IntelliJ IDEAは、バージョン 2016.2から、JUnit Platform上でのテスト実行をサポートしています。
詳細については、 IntelliJ IDEAブログの記事 をご覧ください。
しかしながら、2017.3以降のバージョンのIDEAを使うことをお薦めします。
というのも、これらの新しいバージョンのIDEAは、プロジェクト内で使われているAPIバージョンに応じて
次のJARを自動でダウンロードしてくれるからです:
junit-platform-launcher
、 junit-jupiter-engine
、 junit-vintage-engine
。
IDEA 2017.3より前のIntelliJ IDEAは、 特定のバージョンのJUnit 5をバンドルします。 そのため、新しいバージョンのJUnit Jupiterを使いたい場合、 IDE内でのテスト実行はバージョンの衝突によって失敗する恐れがあります。 そのような場合、ItelliJ IDEAにバンドルされたものではない新しいJUnit 5を 使うために下の説明に従ってください。 |
異なるJUnit 5のバージョン(例えば、 5.2.0 )を使うには、
対応するバージョンの junit-platform-launcher
と junit-jupiter-engine
、
junit-vintage-engine
のJARをクラスパスに含める必要があるかもしれません。
// Only needed to run tests in a version of IntelliJ IDEA that bundles older versions
testRuntime("org.junit.platform:junit-platform-launcher:1.2.0")
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.2.0")
testRuntime("org.junit.vintage:junit-vintage-engine:5.2.0")
<!-- Only needed to run tests in a version of IntelliJ IDEA that bundles older versions -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
4.1.2. Eclipse
Eclipse IDEは、Eclipse Oxygen.1a (4.7.1a)から JUnit Platformサポートを提供しています。
EclipseでJUnit 5を使うためのさらなる情報については、 Eclipse Project Oxygen.1a (4.7.1a) - New and Noteworthy 記事にある公式 Eclipse support for JUnit 5 の章をご覧ください。
4.1.3. 他のIDE
この記事を書いている時点では、IDEAとEclipseを除き、IDE内でJUnit Platform上のテスト実行を 直接サポートしているIDEはありません。しかしながら、いまいまでも自身のIDEでJUnit 5を 試すことができるように、JUnitチームは2つの仲介的な解決法を提供しています。 それは コンソール・ラウンチャー を手動で使うか、 JUnit 4ベースのランナー でテストを実行することができます。
4.2. ビルドサポート
4.2.1. Gradle
バージョン 4.6 から、
GradleはJUnit Platform上でのテスト実行を
ネイティブ・サポート しています。
それを有効化するためには、 build.gradle
内の test
タスク宣言で
useJUnitPlatform()
を指定するだけです。
test {
useJUnitPlatform()
}
タグやエンジンによるフィルタリングもサポートされています:
test {
useJUnitPlatform {
includeTags 'fast', 'smoke & feature-a'
// excludeTags 'slow', 'ci'
includeEngines 'junit-jupiter'
// excludeEngines 'junit-vintage'
}
}
オプションの包括的なリストについては、 Gradleの公式ドキュメント を参照してください。
JUnit Platform Gradleプラグインは非推奨
JUnitチームによって開発された非常に初歩的な |
設定パラメータ
標準的なGralde test
タスクは現在、テスト発見や実行に役立つJUnit Platform
設定パラメータ を設定するための専用DSLを提供していません。
しかしながら、(下に示すように)システムプロパティや
junit-platform.properties
を通して、ビルドスクリプト内で設定パラメータを提供できます。
test {
// ...
systemProperty 'junit.jupiter.conditions.deactivate', '*'
systemProperties = [
'junit.jupiter.extensions.autodetection.enabled': 'true',
'junit.jupiter.testinstance.lifecycle.default': 'per_class'
]
// ...
}
テストエンジンの設定
テストを実行するには、 TestEngine
実装をクラスパス上に配置する必要があります。
JUnit Jupiterベースのテストへのサポートを設定するには、
次のように、 testCompile
の依存関係にJUnit Jupiter APIと、
testRuntime
の依存関係にJUnit Jupiter TestEngine
実装を設定する必要があります。
dependencies {
testCompile("org.junit.jupiter:junit-jupiter-api:5.2.0")
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.2.0")
}
JUnit Platformは、次のように、 testCompile
の依存関係にJUnit 4と、
testRuntime
の依存関係にJUnit Vintage TestEngine
実装を設定することで、
JUnit 4ベースのテストを実行することができます。
dependencies {
testCompile("junit:junit:4.12")
testRuntime("org.junit.vintage:junit-vintage-engine:5.2.0")
}
ログの設定(オプション)
JUnitは、 JUL で知られている java.util.logging
パッケージ内の
Java Logging APIsを使って、警告とデバッグ情報を排出しています。
設定オプションについては、 LogManager
の公式ドキュメントを参照してください。
その他に、ログメッセージを Log4J や
Logback といった
他のロギングフレームワークにログメッセージをリダイレクトすることもできます。
LogManager
の
カスタム実装を提供しているロギングフレームワークを使うには、
java.util.logging.manager
システムプロパティに、使う LogManager
実装の 完全修飾クラス名 を設定してください。
下の例では、Log4j 2.xの設定方法を示しています
(詳細については、 Log4J JDK ロギングアダプタ をご覧ください)。
test {
systemProperty 'java.util.logging.manager', 'org.apache.logging.log4j.jul.LogManager'
}
他のロギングフレームワークは、 java.util.logging
を使ってログされた
メッセージをリダイレクトするための他の方法を提供しています。
例えば、 Logback では、
実行時のクラスパスに依存関係を追加することで、
JULからSLF4Jへの橋渡しを使うことができます。
4.2.2. Maven
JUnitチームは、 mvn test
を通してJUnit 4とJUnit Jupiterのテストを実行できるような
Maven Surefireのための基本的なプロバイダを開発しました。
junit5-jupiter-starter-mavenプロジェクト内の pom.xml
ファイルは、開始ポイントとしてのプロバイダの利用方法を示しています。
unit-platform-surefire-provider を用いて Maven Surefire 2.21.0 を使ってください。
|
...
<build>
<plugins>
...
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>{surefire-version}</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>{platform-version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
...
テストエンジンの設定
Maven Surefireでテストを実行するには、
TestEngine
実装を実行時クラスパスに加える必要があります。
JUnit Jupiterベースのテストへのサポートを設定するには、
次のように、JUnit Jupiter API上に test
の依存関係を設定し、
maven-surefire-plugin
の依存関係にJUnit Jupiter TestEngine
実装を追加してください。
...
<build>
<plugins>
...
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>{surefire-version}</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>{platform-version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>{jupiter-version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
...
<dependencies>
...
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>{jupiter-version}</version>
<scope>test</scope>
</dependency>
</dependencies>
...
JUnit Platform Surefire Providerは、
次のように、JUnit 4上に`test`の依存関係を設定し、 maven-surefire-plugin
の依存関係に
JUnit Vintage `TestEngine`実装を追加することで、JUnit 4ベースのテストを実行できます。
...
<build>
<plugins>
...
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>{platform-version}</version>
</dependency>
...
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>{vintage-version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
...
<dependencies>
...
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>{junit4-version}</version>
<scope>test</scope>
</dependency>
</dependencies>
...
4.2.3. 単一のテストクラスを実行
JUnit Platrform Surefire Providerは、Maven Surefire Pluginによってサポートされている
test
JVMシステムプロパティをサポートしています。
例えば、 org.example.MyTest
内のテストメソッドだけを実行するには、
コマンドラインから mvn -Dtest=org.example.MyTest test
を実行してください。
さらなる詳細については、
Mavne Surefire Pluginのドキュメントを参照してください。
4.2.4. テストクラス名によるフィルタリング
Maven Surefire Pluginは次のパターンにマッチする完全修飾名を持つテストクラスを スキャンします。
-
*/Test.java
-
**/*Test.java
-
**/*Tests.java
-
**/*TestCase.java
さらに、全てのネストされたクラス(静的なメンバークラスも含む)はデフォルトでは除外されます。
しかしながら、このデフォルトの振る舞いは、 include
と exclude
の規則を
pom.xml
明示的に設定にすることで、上書きできます。
例えば、Maven Surefireに静的なメンバークラスを除外させないためには、除外規則を上書きします。
...
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>{surefire-version}</version>
<configuration>
<excludes>
<exclude/>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
...
詳細については、Maven Surefireの テストの包含と除外 のドキュメントをご覧ください。
タグによるフィルタリング
次の設定プロパティを用いることで、タグまたは タグ表現 によって テストをフィルターすることができます。
-
タグ または タグ表現 を算入するには、
groups
またはincludeTags
を使ってください。 -
タグ または タグ表現 を除外するには、
excludedGroups
またはexcludeTags
を使ってください。
...
<build>
<plugins>
...
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<configuration>
<properties>
<includeTags>acceptance | !feature-a</includeTags>
<excludeTags>integration, regression</excludeTags>
</properties>
</configuration>
<dependencies>
...
</dependencies>
</plugin>
</plugins>
</build>
...
設定パラメータ
configurationParameters
プロパティを宣言し、
(下に示すように)Java Properties
ファイル構文を使うか
junit-platform.properties
ファイルを通してキーバリューペアを提供することで、
テスト探索と実行に影響を与えるJUnit Platform 設定パラメータ をセットすることができます。
...
<build>
<plugins>
...
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<configuration>
<properties>
<configurationParameters>
junit.jupiter.conditions.deactivate = *
junit.jupiter.extensions.autodetection.enabled = true
junit.jupiter.testinstance.lifecycle.default = per_class
</configurationParameters>
</properties>
</configuration>
<dependencies>
...
</dependencies>
</plugin>
</plugins>
</build>
...
4.2.5. Ant
Ant のバージョン 1.10.3
から、
新しい junitlauncher
タスクが
導入され、JUnit Platform上でのテスト実行がネイティブサポートされました。
junitlauncher
タスクは単独で、JUnit Platformを起動して選択されたテストコレクションを
渡します。JUnit Platformはその後、テストの探索と実行を登録されたテストエンジンに委譲します。
junitlauncher
タスクは、
リソースコレクション
といったネイティブなAnt構造になるべく密接に連携しています。
これにより、ユーザがテストエンジンに実行してほしいテストを選択する際、
他多数のコアなAntタスクと比べて、一貫して自然な感覚を与えています。
Ant 1.10.3にある junitlauncher タスクのバージョンは、
JUnit Platformを起動するための基本的で最小のサポートを提供しています。
追加的な機能強化(分離したJVMへのテスト分岐のサポートも含む)が
次のAntのリリースで利用可能になる予定です。
|
junit5-jupiter-starter-ant
プロジェクト内の build.xml
は、
開始ポイントとしてタスクの利用方法を示しています。
基本的な使い方
次の例は、 junitlauncher
タスクに1つのテストクラス
(つまり、 org.myapp.test.MyFirstJUnit5Test
)を選ぶための設定方法を示しています。
<path id="test.classpath">
<!-- The location where you have your compiled classes -->
<pathelement location="${build.classes.dir}" />
</path>
<!-- ... -->
<junitlauncher>
<classpath refid="test.classpath" />
<test name="org.myapp.test.MyFirstJUnit5Test" />
</junitlauncher>
test
要素を使うことで、実行したいテストを1つ選択できます。
classpath
要素はを使うことで、JUnit Platformを起動するために使うクラスパスを指定できます。
このクラスパスは、実行に加えるテストクラスを配置するためにも使われます。
次の例は、複数の位置からテストクラスを選ぶための junitlauncher
タスクの
設定方法を示しています。
<path id="test.classpath">
<!-- The location where you have your compiled classes -->
<pathelement location="${build.classes.dir}" />
</path>
....
<junitlauncher>
<classpath refid="test.classpath" />
<testclasses outputdir="${output.dir}">
<fileset dir="${build.classes.dir}">
<include name="org/example/**/demo/**/" />
</fileset>
<fileset dir="${some.other.dir}">
<include name="org/myapp/**/" />
</fileset>
</testclasses>
</junitlauncher>
上の例では、 testclasses
要素によって、異なる位置にある複数のテストクラスを選択しています。
用法と設定オプションのさらなる詳細については、 `junitlauncher`タスク に関するAntの公式ドキュメントをご覧ください。
4.3. コンソールラウンチャー
ConsoleLauncher
はコマンドラインJavaアプリケーションで、
JUnit Platformをコンソールから起動できます。
例えば、JUnit VintageとJUnit Jupiterテストを実行し、
テストの実行結果をコンソールに出力します。
全ての依存関係が含まれた実行可能な junit-platform-console-standalone-1.2.0.jar
は、
Mavenセントラルレポジトリ内の
junit-platform-console-standalone
ディレクトリ以下に公開されています。
スタンドアローンな ConsoleLauncher
は次のように 実行できます。
java -jar junit-platform-console-standalone-1.2.0.jar <Options>
これが出力の例です。
├─ JUnit Vintage
│ └─ example.JUnit4Tests
│ └─ standardJUnit4Test ✔
└─ JUnit Jupiter
├─ StandardTests
│ ├─ succeedingTest() ✔
│ └─ skippedTest() ↷ for demonstration purposes
└─ A special test case
├─ Custom test name containing spaces ✔
├─ ╯°□°)╯ ✔
└─ 😱 ✔
Test run finished after 64 ms
[ 5 containers found ]
[ 0 containers skipped ]
[ 5 containers started ]
[ 0 containers aborted ]
[ 5 containers successful ]
[ 0 containers failed ]
[ 6 tests found ]
[ 1 tests skipped ]
[ 5 tests started ]
[ 0 tests aborted ]
[ 5 tests successful ]
[ 0 tests failed ]
終了コード
ConsoleLauncher は、いずれかのコンテナまたはテストが失敗した場合は
ステータスコード 1 、そうでない場合はステータスコード 0 を返します。
|
4.3.1. オプション
オプション 説明
------ -----------
-h, --help ヘルプ情報を表示します。
--disable-ansi-colors 出力でANSIカラーを無効化します。(全ての
ターミナルではサポートされていません。)
--details <[none,summary,flat,tree,verbose] テスト実行時の出力詳細モードを選択します。
> 次のうち、どれか1つを使います: [none, summary, flat,
tree, verbose]. 'none'が選択された場合、
サマリーとテスト失敗のみが表示されます。 (default: tree)
--details-theme <[ascii,unicode]> テスト実行時の出力詳細ツリーのテーマを選択します。
次のうち、どれか1つを使います: [ascii, unicode]
(default: unicode)
--class-path, --classpath, --cp <Path: 追加的なクラスパスエントリを提供します。 --
path1:path2:...> 例えば、テストエンジンとその依存関係を追加でき
ます。このオプションは繰り返し可能です。
--reports-dir <Path> 特定のローカルディレクトリへのレポート出力を有効化
します。(ディレクトリが存在しない場合は生成されます。)
--scan-modules 実験的:テスト発見で用いる全ての解決済みモジュールを
スキャンします。
-o, --select-module <String: module name> 実験的:テスト発見で用いる1つのモジュールを
選択します。このオプションは繰り返し可能です。
--scan-class-path, --scan-classpath [Path: クラスパスまたは明示的なクラスパスのルート上の
path1:path2:...] 全てのディレクトリをスキャンします。引数がない場合、
-cp(ディレクトリとJARファイル)を通して供給された
追加的なクラスパスエントリと同様にシステムクラスパス
のみがスキャンされます。
クラスパス上にない明示的なクラスパスのルートは、
静かに無視されます。このオプションは繰り返し可能です。
-u, --select-uri <URI> テスト発見のためのURIを選択します。
このオプションは繰り返し可能です。
-f, --select-file <String> テスト発見のためのファイルを選択します。
このオプションは繰り返し可能です。
-d, --select-directory <String> テスト発見のためのディレクトリを選択します。
このオプションは繰り返し可能です。
-p, --select-package <String> テスト発見のためのパッケージを選択します。
このオプションは繰り返し可能です。
-c, --select-class <String> テスト発見のためのクラスを選択します。
このオプションは繰り返し可能です。
-m, --select-method <String> テスト発見のためのメソッドを選択します。
このオプションは繰り返し可能です。
-r, --select-resource <String> テスト発見のためのクラスパス・リソースを選択します。
このオプションは繰り返し可能です。
-n, --include-classname <String> 完全修飾名がマッチするクラスのみを内包するための
正規表現を提供します。不要なクラス読み込みを避ける
ために、デフォルトのパターンは、クラス名が"Test"で始まるか、
"Test"または"Tests"で終わるクラスのみを内包
します。このオプションが繰り返し使われる場合、全ての
パターンはORを使って結合されます。
(default: ^(Test.*|.+[.$]Test.*|.*Tests?)$)
-N, --exclude-classname <String> 完全修飾名がマッチするクラスのみを除外するための
正規表現を提供します。このオプションが繰り返し使われる
場合、全てのパターンはORを使って結合されます。
--include-package <String> テスト実行に含まれるパッケージを提供します。
このオプションは繰り返し可能です。
--exclude-package <String> テスト実行から除外するパッケージを提供します。
このオプションは繰り返し可能です。
-t, --include-tag <String> タグがマッチするテストのみを内包するための
タグまたはタグ表現を提供します。このオプションが繰り返し
使われる場合、全てのパターンはORを使って結合され
ます。
-T, --exclude-tag <String> タグがマッチするテストのみを除外するための
タグまたはタグ表現を提供します。このオプションが繰り返し
使われる場合、全てのパターンはORを使って結合され
ます。
-e, --include-engine <String> テスト実行で内包するエンジンのIDを提供します。
このオプションは繰り返し可能です。
-E, --exclude-engine <String> テスト実行から除外するエンジンのIDを提供します。
このオプションは繰り返し可能です。
--config <key=value> テスト発見・実行のための設定パラメータをセットします。
このオプションは繰り返し可能です。
4.4. JUnit 4を用いてJUnit Platformを実行
JUnitPlatform
ランナーは、JUnit 4ベースの Runner
であり、
JUnit 4環境内のJUnit Platform上でサポートされているプログラミングモデルを持つ
テスト(例えば、JUnit Jupiterテストクラス)は全て実行できます。
@RunWith(JUnitPlatform.class)
をクラスに付与することで、
JUnit 4をサポートしているもののJUnit Platformはまだサポートしていない
IDEやビルドシステムであっても実行できます。
JUnit PlatformはJUnit 4にはない特徴を持っているので、
そのランナーはJUnit Platformのサブセットのみをサポートしています。
特に、レポート機能についてが当てはまります( 表示名vs技術的な名称 をご覧ください)。
しかし、さしあたり JUnitPlatform ランナーはとっかかりやすい方法です。
|
4.4.1. セットアップ
次のアーティファクトとそれらの依存関係がクラスパス上に必要です。 グループIDやアーティファクトID、バージョンに関する詳細は 依存関係のメタデータ をご覧ください。
明示的な依存関係
-
test スコープ内での
junit-platform-runner
:JUnitPlatform
ランナーの位置 -
test スコープ内での
junit-4.12.jar
: JUnit 4を用いたテスト実行 -
test スコープ内での
junit-jupiter-api
:@Test
などを含むJUnit Jupiterを用いてテストを書くためのAPI -
test runtime スコープ内での
junit-jupiter-engine
: JUnit Jupiterのための`TestEngine`実装
推移的な依存関係
-
test スコープ内での
junit-platform-suite-api
-
test スコープ内での
junit-platform-launcher
-
test スコープ内での
junit-platform-engine
-
test スコープ内での
junit-platform-commons
-
test スコープ内での
opentest4j
4.4.2. 表示名vs技術的な名称
@RunWith(JUnitPlatform.class)
を通して実行するクラスに
カスタム 表示名 を定義するには、単に @SuiteDisplayName
を
クラスに付与してカスタム値を提供するだけです。
デフォルトでは、 表示名 はテストアーティファクトのために使われます。
しかしながら、GradleやMavenといったビルドツールでテスト実行するために
JUnitPlatrform
ランナーが使われる場合、生成されたテストレポートはしばしば、
テストクラス名や特殊文字を含むカスタム表示名のような短い表示名の代わりに、
テストアーティファクトの 技術的な名称 (例えば、完全修飾クラス名)を含む必要があります。
レポート目的のために技術的な名称を有効化するには、
単に @RunWith(JUnitPlatform.class)
と一緒に
@UseTechnicalNames
アノテーションを宣言するだけです。
@UseTechnicalNames
の存在は、 @SuiteDisplayName
を通して設定された
いかなるカスタム表示名も上書きすることに注意してください。
4.4.3. 単一のテストクラス
JUnitPlatform
ランナーを使う1つの方法は、
テストクラスに直接 @RunWith(JUnitPlatform.class)
を付与することです。
次の例にあるテストメソッドは org.junit.Test
(JUnit Vintage)ではなく、
org.junit.jupiter.api.Test
(JUnit Jupiter)が付与されていることに注意してください。
さらにこの場合では、テストクラスは public
でないといけません。
そうでないと、IDEやビルドツールがJUnit 4のテストクラスとして認識しない恐れがあります。
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
@RunWith(JUnitPlatform.class)
public class JUnit4ClassDemo {
@Test
void succeedingTest() {
/* no-op */
}
@Test
void failingTest() {
fail("Failing for failing's sake.");
}
}
4.4.4. テストスイート
複数のテストクラスがある場合、次の例のようにテストスイートを作成できます。
import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.SuiteDisplayName;
import org.junit.runner.RunWith;
@RunWith(JUnitPlatform.class)
@SuiteDisplayName("JUnit 4 Suite Demo")
@SelectPackages("example")
public class JUnit4SuiteDemo {
}
JUnit4SuiteDemo
は、 example
パッケージとそのサブパッケージ内にある
全てのテストを発見・実行します。デフォルトでは、 Test
で始まるか、 Test
または Tests
で終わる名前を持つテストクラスのみが含まれます。
追加的な設定オプション
@SelectPackages だけでなく、テスト発見・フィルタリング用の
多くの設定オプションがあります。詳細については、 Javadocをご覧ください。
|
4.5. 設定パラメータ
どのテストクラスとテストエンジンを内包するか、どのパッケージをスキャンするか、
などをプラットフォームに知らせるのに加えて、特定のテストエンジンや登録した拡張を
指定するための追加的なカスタム設定パラメータの提供が必要な場合があります。
例えば、JUnit Jupiter TestEngine
は次のユースケースのための 設定パラメータ を
サポートしています。
設定パラメータ はテキストベースのキーバリューペアで、次の仕組みの内の1つを通して JUnit Platform上で実行しているテストエンジンに供給されます。
-
configurationParameter()
とconfigurationParameters()
メソッド。Launcher
API に供給するリクエストを構築するLauncherDiscoveryRequestBuilder
内に実装されています。 JUnit Platformによって提供されているツールの内の1つを通してテスト実行している場合、 次のように、設定パラメータを指定できます。-
コンソール・ラウンチャー :
--config
コマンドラインオプションを利用。 -
Gradle plugin :
configurationParameter
またはconfigurationParameters
のDSLを利用。 -
Maven Surefire provider :
configurationParameters
プロパティを利用。
-
-
JVMシステムプロパティ。
-
JUnit Platform設定ファイル:
junit-platform.properties
という名前で クラスパスのルートに置かれたJavaのProperties
ファイルの構文規則に従ったファイル。
設定パラメータは上で定義された順序で探索されます。結果として、 Launcher に直接供給された設定パラメータは、システムプロパティと設定ファイルを通して供給されたパラメータよりも優先して使われます。同じように、システムプロパティを通して供給された設定パラメータは、設定ファイルを通して供給された設定パラメータよりも優先して使われます。
|
4.6. タグ表現
タグ表現は、ブーリアン(boolean)表現で、
!
と &
、 |
オペレータが使われます。
さらに、 (
と )
もオペレータの優先順位を調節するために使われます。
オペレータ | 意味 | 結合性 |
---|---|---|
! |
not |
右 |
& |
and |
左 |
| |
or |
左 |
いくつかの観点でテストにタグ付けしている場合、タグ表現は実行するテストの選択を助けてくれます。 テストタイプ(例えば、 micro や integration 、 end-to-end )や 特徴( foo や bar 、 baz )によってタグ付けするときは、 次のタグ表現が役に立つでしょう。
タグ表現 | 選択 |
---|---|
foo |
foo の全てのテスト |
bar | baz |
bar の全てのテストに加えて baz の全てのテスト |
bar & baz |
bar と baz 相互に関わる全てのテスト |
foo & !end-to-end |
foo のうち、 end-to-end でない全てのテスト |
(micro | integration) & (foo | baz) |
foo または baz のうち、 micro または integration の全てのテスト |
5. 拡張モデル
5.1. 概要
JUnit 4において競合している Runner
と @Rule
、 @ClassRule
の
拡張ポイントとは対照的に、JUnit Jupiterの拡張モデルは単一の包括的なコンセプト:
Extension
APIからなっています。しかしながら、 Extension
自体はただの
マーカーインターフェイスであることに注意してください。
5.2. 拡張の登録
拡張は、 @ExtendWith
を通して 宣言的に 、
または @RegisterExtension
を通して プログラム的に 、
もしくはJavaの ServiceLoader
機構を通して 自動的に 登録されます。
5.2.1. 宣言的な拡張登録
テストインターフェイスまたはテストクラス、テストメソッド、カスタム
合成アノテーション に
@ExtendWith(…)
を付与し、登録する拡張のクラス参照を供給することで、
1つ以上の拡張を 宣言的に 登録できます。
例えば、特定のテストメソッドのためにカスタム RandomParametersExtension
を
登録するには、次のようにテストメソッドにアノテーションを付与します。
@ExtendWith(RandomParametersExtension.class)
@Test
void test(@Random int i) {
// ...
}
特定のクラスとそのサブクラスの全てのテストにカスタム RandomParametersExtension
を
登録するには、次のようにテストクラスにアノテーションを付与します。
@ExtendWith(RandomParametersExtension.class)
class MyTests {
// ...
}
複数の拡張はこのように一緒に登録できます:
@ExtendWith({ FooExtension.class, BarExtension.class })
class MyFirstTests {
// ...
}
他には、複数の拡張はこのように分けて登録できます:
@ExtendWith(FooExtension.class)
@ExtendWith(BarExtension.class)
class MySecondTests {
// ...
}
拡張登録の順序
@ExtendWith を通した宣言的に登録された拡張は、
ソースコード内で宣言された順番で実行されます。
例えば、 MyFirstTests と MySecondTests のテストの実行は、
FooExtension と BarExtension によって、 正確にこの順番で 拡張されます。
|
5.2.2. プログラム的な拡張登録
テストクラスのフィールドに @RegisterExtension
を付与することで、
プログラム的に 拡張を登録できます。
@ExtendWith
を通して
宣言的に 拡張を登録した場合、概してアノテーション通りにのみ設定できます。
それに対して、 @RegisterExtension
を通して拡張を登録した場合、
プログラム的に 拡張することができます。
例えば、拡張のコンストラクタや静的なファクトリメソッド、ビルダーAPIに
引数を渡すために使うことができます。
@RegisterExtension フィールドは、 private や null (評価時)であってはなりませんが、
static であっても非静的であっても構いません。
|
静的なフィールド
@RegisterExtension
フィールドが static
である場合、
@ExtendWith
を通したクラスレベルでの拡張が登録された後に、拡張は登録されます。
そのような 静的な拡張
は、拡張API内で実装できることを制限するものではありません。
静的なフィールドを通して登録された拡張はそのため、 BeforeEachCallback
のような
メソッドレベルの拡張APIと同様に、 BeforeAllCallback
や AfterAllCallback
、
TestInstancePostProcessor
のようなクラスレベルとインスタンスレベルの拡張APIを実装します。
次の例では、テストクラスの server
フィールドが、 WebServerExtension
によって
サポートされているビルダーパターンを用いてプログラム的に初期化されます。
設定された WebServerExtension
は、クラスレベルの拡張として自動的に登録されます。
例えば、クラスの全てのテストの実行前にサーバを起動し、実行完了後にサーバを停止するためです。
さらに、 @BeforeEach
や @AfterEach
、 @Test
メソッドだけでなく、
@BeforeAll
や @AfterAll
が付与された静的なライフサイクルメソッドは、
必要であれば、 server
フィールドを通して拡張のインスタンスにアクセスできます。
class WebServerDemo {
@RegisterExtension
static WebServerExtension server = WebServerExtension.builder()
.enableSecurity(false)
.build();
@Test
void getProductList() {
WebClient webClient = new WebClient();
String serverUrl = server.getServerUrl();
// Use WebClient to connect to web server using serverUrl and verify response
assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());
}
}
インスタンスフィールド
@RegisterExtension
フィールドが非静的(つまり、インスタンスフィールド)である場合、
テストクラスが初期化された後、さらに、登録された各 TestInstancePostProcessor
が
テストインスタンスを事後処理されてから
(使われる拡張のインスタンスをアノテーションが付与されたフィールドに潜在的に挿入します)、
拡張は登録されます。そのため、そのような インスタンス拡張 が 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の java.util.ServiceLoader
機構を通じた
グローバル拡張登録 もサポートしており、サードパーティによる拡張を自動で検出し、
クラスパス上で利用可能なものを元に自動で登録します。
特に、カスタム拡張は、同封するJARファイルの中で /META-INF/services
フォルダ内の
org.junit.jupiter.api.extension.Extension
という名前のファイルで
完全修飾クラス名を供給することによって、登録することができます。
自動拡張検出の有効化
自動検出は先進的な特徴で、そのためデフォルトでは有効化されていません。
有効化するには、単に junit.jupiter.extensions.autodetection.enabled
設定パラメータ に
true
をセットするだけです。これは、JVMシステムプロパティや Launcher
に渡される
LauncherDiscoveryRequest
内の 設定パラメータ として、
またはJUnit Platform設定ファイル(詳細は 設定パラメータ をご覧ください)を通しても供給することが可能です。
例えば、拡張の自動検出を有効にするには、JVMを次のシステムプロパティで起動します。
-Djunit.jupiter.extensions.autodetection.enabled=true
自動検出が有効化されている場合、JUnit JUpiterのグローバル拡張
(つまり TestInfo
や TestReporter
などへのサポート)の後に、
ServiceLoader
機構を通して発見された拡張は拡張レジストリに追加されます。
5.2.4. 拡張の継承
登録された拡張は、トップダウンに従ってテストクラス階層の中で継承されます。 同様に、クラスレベルで登録された拡張は、メソッドレベルで継承されます。 さらに、特定の拡張実装は、ある拡張コンテキストと親コンテキストに対して1度だけ登録されます。 その結果として、重複する拡張実装の登録は無視されます。
5.3. 条件付きテスト実行
ExecutionCondition
は、プログラム的な 条件付きテスト実行 のための
Extension
APIを定義しています。
ExecutionCondition
は、各コンテナ(つまり、テストクラス)が含む全てのテストを
実行すべきか、供給された ExecutionContext
に則って決定するために 評価 されます。
同じように ExecutionCondition
は、各テストごとに供給された ExecutionContext
に則って
実行されるか決定するために 評価 されます。
複数の ExecutionCondition
が登録されている場合、コンテナもしくはテストは、
条件のうち1つでも disabled を返した瞬間に無効化されます。
そのため、条件が評価されるかどうかの保証はありません。
なぜなら、他の条件が既にコンテナもしくはテストを無効化している可能性があるからです。
つまり、評価は短絡ブーリアンORオペレータのように作動します。
具体的な例については、 DisabledCondition
と @Disabled
のソースコードをご覧ください。
5.3.1. 条件の無効化
時には、いくつかの条件を有効に しないで テストスイートを実行することが役に立ちます。
例えば、 @Disabled
がたとえ付与されていたとしても、それらが 壊れていないか
確認するために実行したいかもしれません。
このためには、単に junit.jupiter.conditions.deactivate
設定パラメータ に
現在のテスト実行でどの条件を無効化する(つまり、評価しない)かを指定するパターンを提供するだけです。
パターンは、JVMシステムプロパティや、 Launcher
に渡される LauncherDiscoveryRequest
内の
設定パラメータ として、もしくはJUnit Platform設定ファイル
(詳細は 設定パラメータ をご覧ください)を
通して供給することができます。
例えば、JUnitの @Disabled
条件を無効化するには、
次のシステムプロパティでJVMを起動します。
-Djunit.jupiter.conditions.deactivate=org.junit.*DisabledCondition
パターンマッチングの構文
junit.jupiter.conditions.deactivate
パターンが、アスタリスク( *
)のみで
構成されている場合、全ての条件が無効化されます。そうでない場合、パターンは、
各登録された条件の完全修飾クラス名( FQCN )とマッチしているかの判断に使われます。
パターン内の全てのドット( .
)は、FQCN内のドット( .
)またはドル( $
)と
マッチします。全てのアスタリスク( *
)は、FQCN内の1つ以上の文字とマッチします。
パターン内の他の全ての文字は、FQCNと1対1でマッチします。
例:
-
*
:全ての条件を無効化します。 -
org.junit.*
:org.junit
パッケージとサブパッケージ以下の全ての条件を無効化します。 -
*.MycCondition
:クラス名がMyCondition
のものを無効化します。 -
System
:クラス名にSystem
を含むものを無効化します。 -
org.example.MyCondition
:FQCNがorg.example.MyCondition
のものを無効化します。
5.4. テストインスタンスの事後処理
TestInstancePostProcessor
は、テストインスタンスを 事後処理 したい Extensions
のAPIを定義しています。
一般的なユースケースには、テストインスタンスに依存関係を挿入したり、 テストインスタンス上でカスタム初期化メソッドを呼び出すことなどが含まれます。
具体的な例については、 MockitoExtension
と SpringExtension
のソースコードをご覧ください。
5.5. パラメータの解決
ParameterResolver
は、実行時にパラメータを動的に解決するための
Extension
APIを定義しています。
テストコンストラクタまたは @Test
や @RepeatedTest
、 @ParameterizedTest
、
@TestFactory
、 @BeforeEach
、 @AfterEach
、 @BeforeAll
、 @AfterAll`メソッドが
パラメータを受け入れている場合、パラメータは `ParameterResolver
によって実行時に
解決される 必要があります。 ParameterResolver`は 、組み込みもの( `https://github.com/junit-team/junit5/tree/{release-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java[TestInfoParameterResolver]
をご覧ください)
まはた ユーザが登録したもの を使うことができます。
一般的には、パラメータは 名前 や 型 、 アノテーション 、それらの組み合わせで解決されます。
CustomTypeParameterResolver
と CustomAnnotationParameterResolver
のソースコードをご覧ください。
JDK 9より前のバージョンのJDKの javac によって生成されるバイトコード内の
バグによって、コアの java.lang.reflect.Parameter APIを通したパラメータに対する
アノテーションの直接の探索は、 内部クラス のコンストラクタ
(例えば、 @Nested テストクラス内のコントラクタ)は常に失敗します。
|
ParameterContext
APIは、そのため、パラメータ上のアノテーションの正しい探索のために、
次の便利なメソッドを含んでいます。拡張に関する開発者(Extension Authors)は、
JDK内のこのバグを避けるために、 java.lang.reflect.Parameter
内で提供されている
メソッドの代わりに、これらのメソッドを使うことが強く奨励されています。
-
boolean isAnnotated(Class<? extends Annotation> annotationType)
-
Optional<A> findAnnotation(Class<A> annotationType)
-
List<A> findRepeatableAnnotations(Class<A> annotationType)
5.6. テストライフサイクルのコールバック
次のインターフェイスは、テスト実行ライフサイクルにおいて様々なポイントで
テストを拡張するためのAPIを定義しています。
実例に関しては次の章を、さらなる詳細については org.junit.jupiter.api.extension
パッケージ内の各インターフェイスのJavadocをご覧ください。
複数の拡張APIsの実装
拡張に関する開発者は、これらのインターフェイスのうちいくつかを1つの拡張内に
実装できます。具体的な例については、 SpringExtension のソースコードをご覧ください。
|
5.6.1. BeforeとAfterのテスト実行コールバック
BeforeTestExecutionCallback
と AfterTestExecutionCallback
はそれぞれ、
テストメソッドが実行される 直前 と 直後 に実行される振る舞いを追加するための
Extension
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.7. 例外処理
TestExecutionExceptionHandler
は、テスト実行中に投げられた例外を処理する
Extensions
のためのAPIを提供しています。
次の例は、 IOException
の全インスタンスを飲み込み、他の型の例外を投げ直す拡張を示しています。
public class IgnoreIOExceptionExtension implements TestExecutionExceptionHandler {
@Override
public void handleTestExecutionException(ExtensionContext context, Throwable throwable)
throws Throwable {
if (throwable instanceof IOException) {
return;
}
throw throwable;
}
}
5.8. テストテンプレートに関する呼び出しコンテキストの提供
@TestTemplate
メソッドは、少なくとも1つの TestTemplateInvocationContextProvider
が
登録されているときにだけ実行されます。
各プロバイダは、 TestTemplateInvocationContext
インスタンスの Stream
を提供する責務を担っています。
各コンテキストは、カスタム表示名と @TestTemplate
メソッドの次の呼び出しのために使われる追加的な拡張のリストを特定します。
次の例は、テストテンプレートの書き方だけでなく、TestTemplateInvocationContextProvider
の登録・実装の方法も示しています。
@TestTemplate
@ExtendWith(MyTestTemplateInvocationContextProvider.class)
void testTemplate(String parameter) {
assertEquals(3, parameter.length());
}
public class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {
@Override
public boolean supportsTestTemplate(ExtensionContext context) {
return true;
}
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
return Stream.of(invocationContext("foo"), invocationContext("bar"));
}
private TestTemplateInvocationContext invocationContext(String parameter) {
return new TestTemplateInvocationContext() {
@Override
public String getDisplayName(int invocationIndex) {
return parameter;
}
@Override
public List<Extension> getAdditionalExtensions() {
return Collections.singletonList(new ParameterResolver() {
@Override
public boolean supportsParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return parameterContext.getParameter().getType().equals(String.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return parameter;
}
});
}
};
}
}
この例では、テストテンプレートは2回呼び出されます。
呼び出しの表示名は、呼び出しコンテキストによって決められたように "foo" と "bar" になります。
各呼び出しは、メソッドパラメータを解決するためのカスタム ParameterResolver
を登録します。
ConsoleLauncher
を使った時の出力は次のようになります。
└─ testTemplate(String) ✔
├─ foo ✔
└─ bar ✔
TestTemplateInvocationContextProvider
拡張APIは、
異なるコンテキストでテストのようなメソッドを繰り返し呼び出すことを当てにしている
別種のテストを実装することを第一に意図しています。
例えば、異なるパラメータを用いたり、テストクラスインスタンスを異なる方法で準備したり、
コンテキストを修正せずに複数回行うなどです。
機能に提供ためにこの拡張ポイントを使う 繰り返しテスト
または パラメータ化テスト の実装を参照してください。
5.9. 拡張での状態の保持
通常、拡張は一度だけインスタンス化されます。
そのため、このような関連する質問が挙がります:拡張のある呼び出しからの状態はどのようにして次の呼び出しに保持されるのか?
ExtensionContext
APIは、まさにこの目的のための Store
を提供します。
拡張は、後からの収集のためにストアに値を入れます。
メソッドレベルのスコープでの Store
の使用例は、
タイミング拡張
をご覧ください。
テスト実行中に ExtensionContext
に貯蔵された値は、
周囲の ExtensionContext
では利用できないことに注意してください。
ExtensionContext
はネストされているかもしれないので、
内部コンテキストのスコープもまた限定的となっています。
Store
を通した値の貯蔵と収集に利用可能なメソッドの詳細については、
対応するJavaDocをご覧ください。
ExtensionContext.Store.CloseableResource CloseableResource のインスタンスで、
close() メソッドの呼び出しによって通知されます。
|
5.10. 拡張でサポートしているユーティリティ
junit-platform-commons
アーティファクトは、アノテーションやクラス、
リフレクション、タスクをスキャンするクラスパスに作動する 保守された ユーティリティメソッドを
org.junit.platform.commons.support
という名前のパッケージを公開しています。
TestEngine
と Extension
の開発者は、JUnit Platformの動作に合わせるため、
これらサポートされたメソッドを利用することが推奨されています。
5.10.1. アノテーションのサポート
AnnotationSupport
は、静的なユーティリティメソッドを提供しており、
アノテーションの付与された要素(つまり、パッケージやアノテーション、クラス、インターフェイス、
メソッド、フィールド)を操作できます。
これらは、ある要素に特定のアノテーションが付与またはメタ付与されているか確認したり、
特定のアノテーションを検索したり、クラスやインターフェイス内でアノテーション付与されている
メソッドとフィールドを探し出すためのメソッドが含まれています。
これらのメソッドのいくつかは、実装されたインターフェイスとクラス階層内を
アノテーションを見つけるために捜索します。
さらなる詳細については、 AnnotationSupport
のJavaDocをご覧ください。
5.10.2. クラスのサポート
ClassSupport
は、クラス(つまり、 java.lang.Class
のインスタンス)に
作動する静的なユーティリティメソッドを提供しています。
さらなる詳細については、 ClassSupport
のJavaDocをご覧ください。
5.10.3. リフレクションのサポート
ReflectionSupport
は、標準のJDKリフレクションとクラス読み込み機構を増強するための
静的なユーティリティメソッドを提供しています。
これらは、特定の述語にマッチするクラスを探すためにクラスパスをスキャンしたり、
クラスの新しいインスタンスを読み込んで生成したり、
メソッドを見つけて呼び出すためのメソッドを含みます。
これらのメソッドのいくつかは、マッチするメソッドの位置を特定するためにクラス階層を走査します。
さらなる詳細については、 ReflectionSupport
のJavaDocをご覧ください。
5.11. ユーザコードと拡張の相対実行順序
1つ以上のテストメソッドを含むテストクラスを実行する時、 ユーザの提供するテストとライフサイクルに関するメソッドに加えて、 いくつかの拡張コールバックが呼び出されます。 次の図は、ユーザ提供のコードと拡張コードの相対順序を示しています。
ユーザ提供のテストとライフサイクルに関するメソッドはオレンジで示され、 拡張によって提供されるコールバックは青で示されています。 灰色のボックスは、1つのテストメソッドの実行を意味しており、 テストクラスの全てのテストメソッドに対して繰り返されます。
次の表は、 ユーザコードと拡張コード の図式内の12のステップについて、より詳しく説明をしています。
ステップ | インターフェイス/アノテーション | 説明 |
---|---|---|
1 |
インターフェイス |
コンテナの全てのテストが実行される前に実行される拡張コード |
2 |
アノテーション |
コンテナの全てのテストが実行される前に実行されるユーザコード |
3 |
インターフェイス |
各テストが実行される前に実行される拡張コード |
4 |
アノテーション |
各テストが実行される前に実行されるユーザコード |
5 |
インターフェイス |
テストが実行される直前に実行される拡張コード |
6 |
アノテーション |
実際のテストメソッドとなるユーザコード |
7 |
インターフェイス |
テスト中に投げられる例外を扱う拡張コード |
8 |
インターフェイス |
テストと対応する例外ハンドラが実行された直後に実行される拡張コード |
9 |
アノテーション |
各テストが実行された後に実行されるユーザコード |
10 |
インターフェイス |
各テストが実行された後に実行される拡張コード |
11 |
アノテーション |
コンテナの全てのテストが実行された後に実行されるユーザコード |
12 |
インターフェイス |
コンテナの全てのテストが実行された後に実行される拡張コード |
最も単純なケースでは、実際のテストメソッドのみが実行されます(ステップ 6)。 他の全てのステップはオプションで、対応するライフサイクルコールバックをサポートするユーザコードまたは拡張コードの存在に依存します。 様々なライフサイクルコールバックのさらなる詳細については、 各アノテーションと拡張それぞれのJavaDocをご覧ください。
6. JUnit 4からの移行
JUnit Jupiterのプログラミングモデルと拡張モデルは、
JUnit 4の Rules
や Runners
といった特徴をネイティブではサポートしていませんが、
ソースコード保守運用者が全ての既存のテストやテスト拡張、カスタムされたビルド・テスト基盤を
JUnit Jupiterに移行するためにアップデートする必要があることは期待していません。
代わりに、JUnitは、JUnit Platformの基盤を用いてJUnit 3とJUnit 4ベースのテストを
実行できる JUnit Vintageテストエンジン を通して、寛大な移行経路を提供しています。
JUnit Jupiterに特化したクラスとアノテーションは全て新しい org.junit.jupiter
ベース
パッケージ以下にあるので、クラスパス上にあるJUnit 4とJUnit Jupiterは
いずれもコンフリクトを起こしません。そのため、JUnit Jupiterテストと一緒に
既存のJUnit 4のテストを保守することは安全です。さらに、JUnitチームはJUnit 4.xベースラインの
保守とバグ修正に関するリリースを提供し続けるので、開発者はスケジュール上では
JUnit Jupiter移行のための時間が多くあります。
6.1. JUnit Platform上でのJUnit 4テストの実行
junit-vintage-engine
アーティファクトがテストの実行時のパスにあるか確認するだけです。
そのような場合、JUnit 3とJUnit 4のテストは、JUnit Platformラウンチャーによって自動的に拾われます。
GradleとMavenでの実施方法は、
junit5-samples
レポジトリのプロジェクト例をご覧ください。
6.1.1. カテゴリのサポート
@Category
が付与されたテストクラスとメソッドに関して、
JUnit Vintageテストエンジン はカテゴリの完全修飾クラス名を
対応するテスト識別子のタグとして扱います。
例えば、テストメソッドに @Category(Example.class)
が付与されている場合、
"com.acme.Example"
のタグが付与されます。
JUnit 4の Categories
ランナーと同じように、この情報は発見されたテストが
実行される前にフィルタリングされるのに使われます
(詳細については テストを実行する をご覧ください)。
6.2. 移行のためのTips
次は、既存のJUnit 4のテストをJUnit Jupiterに移行する際に気をつけるべきことです。
-
org.junit.jupiter.api
パッケージ内のアノテーション -
org.junit.jupiter.api.Assertions
内のアサーション -
org.junit.jupiter.api.Assumptions
内のアサンプション -
@Before
と@After
はもうありません。代わりに@BeforeEach
と@AfterEach
を使ってください。 -
@BeforeClass
と@AfterClass
はもうありません。代わりに@BeforeAll
と@AfterAll
を使ってください。 -
@Ignore
はもうありません。代わりに@Disabled
を使ってください。 -
@Category
はもうありません。代わりに@Tag
を使ってください。 -
@RunWith
はもうありません。代わりに@ExtendWith
を使ってください。 -
@Rule
と@ClassRule
はもうありません。@ExtendWith
によって置換されました。個別のRuleのサポートについては次の章をご覧ください。
6.3. JUnit 4のRuleの限定的なサポート
上で述べたように、JUnit Jupiterは、JUnit 4のRuleを ネイティブではサポートしていませんし、これからもしません。 しかしながら、JUnitチームは、多くの組織(特に、大きい組織)がカスタムRuleを 利用した巨大なJUnit 4のコードベースを持っていることを理解しています。 これらの組織に奉仕し、段階的な移行を可能にするために、 JUnitチームは、言葉通り選択的ですが、いくつかJUnit 4のRuleを JUnit Jupiter内でサポートすることを決めました。 このサポートは、アダプタベースで行われ、JUnit Jupiterの拡張モデルと 意味的に互換のあるRule(つまり、テスト全体の実行フローを完全には変更しないもの)に 限定されています。
JUnit Jupiterの junit-jupiter-migrationsupport
モジュールは、
現在のところ、次の3つの Rule
型とそれらのサブクラスをサポートしています。
-
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のインポート文含めて 変更なしで残す ことができます。
この限られた形での Rule
サポートは、クラスレベルのアノテーション
org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport
によって
切り替えできます。このアノテーションは、 合成アノテーション で、
全ての移行をサポートする拡張( VerifierSupport
と ExternalResourceSupport
、
ExpectedExceptionSupport
)を有効にします。
しかしながら、JUnit 5に対する新たな拡張を開発する際には、 JUnit 4のRuleベースのモデルの代わりに、JUnit Jupiterの新しい拡張モデルを使ってください。
Junit Jupiter内におけるJUnit 4の Rule のサポートは現在のところ、
実験的な 特徴です。詳細については、 実験的なAPI をご覧ください。
|
7. 先進的なトピック
7.1. JUnit PlatformラウンチャーAPI
JUnit 5の主要な目的の一つは、JUnitとそのプログラム的なクライアント(ビルドツールとIDE)間の インターフェイスをより強固で安定したものにすることです。 その目的は、テスト発見と実行の内部を、外界から必要とされる全てのフィルタリングと 設定から切り離すことです。
JUnit 5は、テスト発見・フィルタリング・実行に使うことのできる Launcher
の
コンセプトを導入します。さらに、サードパーティによるテストライブラリ
(SpockやCucumber、FitNesse)は、カスタム TestEngine
を提供することによって
JUnit Platformの起動基盤にプラグインできます。
起動APIは junit-platform-launcher
モジュールにあります。
起動APIの使用例は、 junit-platform-console
プロジェクトの ConsoleLauncher
にあります。
7.1.1. テストの発見
テストディレクトリ をプラットフォーム自身専用の特徴として導入することは、 過去にテストクラスやテストメソッドを特定するのに経験しなければならなかった困難の ほとんどからIDEやビルドツールを(上手くいけば)解放するでしょう。
使用例:
import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
.selectors(
selectPackage("com.example.mytests"),
selectClass(MyTestClass.class)
)
.filters(
includeClassNamePatterns(".*Tests")
)
.build();
Launcher launcher = LauncherFactory.create();
TestPlan testPlan = launcher.discover(request);
現在のところ、クラスとメソッド、あるパッケージ内の全てのクラスを選択することや、 クラスパス内の全てのテストを探索することさえ可能です。 発見は、全ての関係するテストエンジンで行われます。
結果として TestPlan
は、 LauncherDiscoveryRequest
に適合する
全てのエンジンやクラス、テストメソッドの階層的(かつ、読み込み専用)な記述となります。
クライアントは、そのツリーを走査し、ノードに関する詳細を集め、
元ソース(クラスやメソッド、ファイルの位置)へのリンクを入手できます。
テストプランの全てのノードは、 ユニークなID を持っており、
特定のテストやテストグループを呼び出すのに使用できます。
7.1.2. テストの実行
テストを実行するために、クライアントは、発見フェイズと同じ LauncherDiscoveryRequest
を
使うか、新しいリクエストを生成できます。
テスト進行とレポートは、次の例のように Launcher
に1つ以上の
TestExecutionListener
実装を登録することで達成できます。
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
.selectors(
selectPackage("com.example.mytests"),
selectClass(MyTestClass.class)
)
.filters(
includeClassNamePatterns(".*Tests")
)
.build();
Launcher launcher = LauncherFactory.create();
// Register a listener of your choice
TestExecutionListener listener = new SummaryGeneratingListener();
launcher.registerTestExecutionListeners(listener);
launcher.execute(request);
execute()
メソッドに返り値はありませんが、リスナーを用いることで、
容易に自身のオブジェクト内で最終結果を集約することができます。
例については、 SummaryGeneratingListener
をご覧ください。
7.1.3. 自身のテストエンジンをプラグイン
JUnitは現在のところ、2つの利用可能な TestEngine
を提供しています。
-
junit-jupiter-engine
:JUnit Jupiterのコアです。 -
junit-vintage-engine
:JUnit 4の上部に位置する薄いレイヤーで、 古い テストを起動基盤で実行できるようにします。
サードパーティもまた、 junit-platform-engine モジュール内のインターフェイスを実装して
エンジンを 登録する ことで、自身の TestEngine
に貢献する可能性があります。
エンジンの登録は、現在のところ、Javaの java.util.ServiceLoader
機構によって
サポートされています。例えば、 junit-jupiter-engine
モジュールは、
junit-jupiter-engine
JAR中の /META-INF/services
内にある
org.junit.platform.engine.TestEngine
という名前のファイルの
org.junit.jupiter.engine.JupiterTestEngine
を登録します。
7.1.4. 自身のテスト実行リスナーをプラグイン
テスト実行リスナーをプログラム的に登録するためのパブリックな Launcher
APIメソッドに
加えて、Javaの java.util.ServiceLoader
機能を通して実行時に発見される
カスタム TestExecutionListener
実装が自動的に DefaultLauncher
を用いて登録されます。
例えば、 TestExecutionListener
を実装し、
/META-INF/services/org.junit.platform.launcher.TestExecutionListener
ファイル内で宣言されている example.TestInfoPrinter
クラスは、
自動的に読み込まれ登録されます。
8. APIの進化
JUnit 5の大きな目標の1つは、多くのプロジェクトに現在進行形で使われていますが、 JUnitを進化させるために保守運用者ができることを改善させることです。 JUnit 4では、元は内側の構成物として加えられていた多くのものが、 外部の拡張開発者やツールビルダーに使われました。 そのことが、JUnit 4を特に難しく、時には不可能なものにしました。
というわけで、JUnit 5は全ての公開されている利用可能なインターフェイスやクラス、メソッドに 対して、あらかじめ定義したライフサイクルを導入します。
8.1. APIのバージョンとステータス
全ての公開されたアーティファクトは、バージョン番号 <メジャー>.<マイナー>.<パッチ>
を持ち、
全ての公開されている利用可能なインターフェイスまたはクラス、メソッドは、
@API Guardian
プロジェクトから @API
を付与されています。
このアノテーションの status
属性は、次の値のうちの1つを割り当てできます。
ステータス | 説明 |
---|---|
|
JUnit自身以外のコードから使われてはなりません。事前通知なしに削除される恐れがあります。 |
|
もう使うべきではありません。次のマイナーリリースで消滅する恐れがあります。 |
|
フィードバックを求めている新しい実験的な機能を意図しています。この要素は注意を持って使ってください。 |
|
少なくとも 現在のメジャーバージョンにおける次のマイナー・リリースまでは後方非互換に変更されない機能を意図しています。削除予定の場合は、まず |
|
現在のメジャーバージョン( |
@API
アノテーションが型に存在していた場合、
その型の全てのパブリックなメンバーにも同様に適用されると考えられます。
メンバーは、それより低い安定性の異なる status
値を宣言することが許されています。
8.2. 実験的なAPI
次の表は、( @API(status = EXPERIMENTAL)
を通して) 実験的な ものとして
現在のところ指定されているAPIを列挙しています。これらのAPIを使うときは注意してください。
パッケージ名 | クラス名 | タイプ |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8.3. @API ツールのサポート
@API Guardian
プロジェクトは、 @API
の付与された
APIの開発者と利用者のためのツールサポートを提供することを計画しています。
例えば、ツールサポートは、JUnit APIが @API
アノテーションの宣言に応じて
使われているかを確認する手段を提供する見込みです。
9. 貢献者
GitHub上の 現在の貢献者リスト を直接ご覧ください。
10. リリースノート
リリースノートは こちら で利用可能です。