JUnit 5拡張モデル
拡張モデル
JUnit Jupiterには拡張モデルという仕組みが用意されており、拡張機能を簡単に導入できる。
基本拡張モデル
拡張機能を作るには、Extensionインターフェース、またはそのサブインターフェースを実装する。Extension自体はマーカーインターフェースであり、メソッドは定義されていない。
マーカーインターフェースとは、通常のインターフェースと同じだがメソッドを宣言しないインターフェースである。JavaではSerializable、Cloneable、EventListenerなどが代表例で、多くの場合は単純な型チェックに使われる。
拡張ポイントはExtensionのサブインターフェースとして提供され、BeforeEachCallbackやAfterEachCallbackなどがある。
拡張クラスをテストに適用するには@ExtendWithを使用する。クラスに指定すればクラス内の各テストに適用され、メソッドに指定すればそのメソッドだけに適用される。
拡張ポイント
JUnit Jupiterには、実行条件、テストインスタンス生成、後処理、パラメータ解決、ライフサイクルコールバック、テスト結果監視、例外処理、テストテンプレート、手続き的登録、自動登録、データ共有などの拡張ポイントがある。
サポートクラス
拡張クラスを実装するとき、共通操作や例外処理を簡潔にするユーティリティクラスが提供されている。これらは第三者が独自のTestEngineや拡張機能を作るための公開サポートAPIなので、安心して使用できる。
テスト実行条件の制御
ExecutionConditionを実装し、evaluateExecutionCondition(ExtensionContext)からConditionEvaluationResultを返す。戻り値によって対象テストを有効にするか無効にするかを制御する。このコールバックは対象テストが評価されるたびに呼び出される。
テストインスタンス生成
TestInstanceFactoryを実装すると、テストインスタンスの生成方法をカスタマイズできる。createTestInstance()は各テストメソッドの実行前に呼び出され、リフレクション支援機能を使ってオブジェクトを生成できる。
テストインスタンス生成後の初期化
TestInstancePostProcessorを実装すると、生成されたテストインスタンスを初期化できる。依存性注入や初期化メソッド呼び出しに利用できる。
パラメータ解決
ParameterResolverを実装すると、テストメソッド、ライフサイクルメソッド、コンストラクタ、動的テストなどへ引数を提供できる。supportsParameter()で対象パラメータを判定し、resolveParameter()で実際の値を返す。Javaの引数名を取得するには、コンパイル時に-parametersオプションが必要である。
ライフサイクルコールバック
ライフサイクルコールバックインターフェースを使うと、全テスト前、各テスト前、テスト実行前、テスト実行後、各テスト後、全テスト後などのJUnitライフサイクルに処理を差し込める。
テスト結果に応じた処理
TestWatcherを実装すると、成功、失敗、中止、無効化などのテスト結果に反応できる。各メソッドは空実装のdefaultメソッドで、AfterEachCallbackの後に実行される。
テストで発生した例外処理
TestExecutionExceptionHandlerを実装すると、テストメソッドから投げられた例外を処理できる。アサーションエラーも対象になるため注意が必要である。
ライフサイクルメソッドの例外処理
LifecycleMethodExecutionExceptionHandlerを使うと、@BeforeAll、@BeforeEach、@AfterEach、@AfterAllなどのライフサイクルメソッドで発生した例外を処理できる。これはテスト実行中の例外を扱うTestExecutionExceptionHandlerとは別である。
同じテストを異なるコンテキストで実行
@TestTemplateとTestTemplateInvocationContextProviderを使うと、同じテストを異なる呼び出しコンテキストで実行できる。supportsTestTemplate()で対応可否を判断し、provideTestTemplateInvocationContexts()でコンテキストを提供する。各コンテキストは表示名や追加拡張を定義できる。
拡張機能の手続き的登録
@RegisterExtensionは、テストクラスのフィールドを通じて拡張機能を登録する。これにより拡張インスタンスの設定を動的に指定できる。staticフィールドはクラスレベル拡張を登録できるが、インスタンスフィールドではBeforeAllCallbackなど一部のクラスレベルコールバックを利用できない。
ServiceLoaderによる自動登録
拡張機能はJavaのServiceLoaderで自動登録できる。/META-INF/services/org.junit.jupiter.api.extension.Extensionを作成し、拡張実装クラスを列挙する。自動検出は次の設定で有効にする。
junit.jupiter.extensions.autodetection.enabled=true
Extension間のデータ共有
テストが並列実行される可能性がある場合、拡張のインスタンスフィールドで状態を共有するのは避ける。同じ拡張インスタンスが複数テストで再利用されるためである。代わりにExtensionContext.Storeを使用する。
StoreはExtensionContextからNamespace付きで取得する。名前空間を分けることで、同じキーを使う複数拡張の衝突を防げる。すべての拡張で共有したい場合だけNamespace.GLOBALを使う。
Storeのライフサイクルと検索
Storeのライフサイクルは、取得元のExtensionContextと同じである。メソッドレベルのコンテキストには親となるクラスレベルのコンテキストがあり、親コンテキストのStoreに保存した情報は子コンテキストからも参照できる。
ライフサイクル終了時の処理
Storeに保存するオブジェクトはExtensionContext.Store.CloseableResourceを実装できる。対応するStoreのライフサイクルが終了するとclose()が自動的に呼ばれるため、後片付け処理に使える。