前回の続き
JUnitの Extension インターフェース
JUnit4のRunnerと同様、JUnit5も拡張をすることができる。
JUnit5が提供している 拡張インターフェース(Extension を継承したインターフェース)は次のとおり。
ContainerExecutionCondition- メソッド -
evaluate(ContainerExtensionContext)- 戻り値の型 -
ConditionEvaluationResult
- 戻り値の型 -
- テストクラスレベルで実行可否の判断する
- メソッド -
TestExecutionCondition- メソッド -
evaluate(TestExtensionContext)- 戻り値の型 -
ConditionEvaluationResult
- 戻り値の型 -
- テストメソッドの実行可否の判断する
- メソッド -
BeforeAllCallback- メソッド -
beforeAll(ContainerExtensionContext)- 戻り値の型 -
void
- 戻り値の型 -
@BeforeAllの前に実行される
- メソッド -
BeforeEachCallback- メソッド -
beforeEach(TestExtensionContext)- 戻り値の型 -
void
- 戻り値の型 -
@BeforeEachの前に実行される
- メソッド -
BeforeTestExecutionCallback- メソッド -
beforeTestExecution(TestExtensionContext)- 戻り値の型 -
void
- 戻り値の型 -
- テスト(
@Test)の前に実行される
- メソッド -
AfterTestExecutionCallback- メソッド -
afterTestExecution(TestExtensionContext)- 戻り値の型 -
void
- 戻り値の型 -
- テスト(
@Test)の後に実行される
- メソッド -
AfterEachCallback- メソッド -
afterEach(TestExtensionContext)- 戻り値の型 -
void
- 戻り値の型 -
- @AfterEach` の後に実行される
- メソッド -
AfterAllCallback- メソッド -
afterAll(ContainerExtensionContext)- 戻り値の型 -
void
- 戻り値の型 -
@AfterAllの後に実行される
- メソッド -
TestInstancePostProcessor- メソッド -
postProcessTestInstance(Object, ExtensionContext)- 戻り値の型 -
void
- 戻り値の型 -
- テストクラスのインスタンス生成後に実行される
- メソッド -
TestExecutionExceptionHandler- メソッド -
handleTestExecutionException(TestExtensionContext, Throwable)- 戻り値の型 -
void
- 戻り値の型 -
- テスト時に発生した例外の扱いを決定する
- メソッド -
ParameterResolver- メソッド -
supports(ParameterContext, ExtensionContext)- 戻り値の型 -
boolean
- 戻り値の型 -
- メソッド -
resolve(ParameterContext, ExtensionContext)- 戻り値の型 -
Object
- 戻り値の型 -
- テストメソッドに与える引数を解決する
- メソッド -
テストライフサイクル
これらの Extension は次の順番でテストクラスの生成、テストの実行、テスト終了までの一連の流れに組み込まれる。
ContainerExecutionConditionBeforeAllCallback- テストクラスの
@BeforeAll - テストクラスのインスタンス生成
TestInstancePostProcessor
ここから、テストがある場合
TestExecutionConditionBeforeEachCallback- テストクラスの
@BeforeEach BeforeTestExecutionCallbackParameterResolver#supports(引数の数だけ)ParameterResolver#resolve(引数の数だけ)- テストクラスの
@Test(テスト) TestExecutionExceptionHandler(テスト中に例外が発生した場合だけ)AfterTestExecutionCallback- テストクラスの
@AfterEach AfterEachCallback
ここから、同じクラスに他のテストがある場合
- 次のテストのインスタンス生成
TestInstancePostProcessorTestExecutionCondition- ...
AfterEachCallback
ここから、ネストしたクラスがある場合
ContainerExecutionConditionBeforeAllCallback- アウタークラスのインスタンス生成
TestInstancePostProcessor- インナークラスのインスタンス生成
TestInstancePostProcessorTestExecutionConditionBeforeEachCallback- アウタークラスの
@BeforeEach - インナークラスの
@BeforeEach BeforeTestExecutionCallbackParameterResolver#supports(引数の数だけ)ParameterResolver#resolve(引数の数だけ)- テストクラスの
@Test(テスト) TestExecutionExceptionHandler(テスト中に例外が発生した場合だけ)AfterTestExecutionCallback- インナークラスの
@AfterEach - アウタークラスの
@AfterEach AfterEachCallback
ここから、インナークラスにテストがない場合
AfterAllCallback
ここから、他にテストがない場合
- テストクラスの
@AfterAll AfterAllCallback
確認コードはこちらにある、 NestedClasses.java/NoTest.java/TestLifecycleCallbacks.java を参照。
Extension の登録
実装した Extension は @ExtendWith アノテーションをクラスまたはメソッドに指定することによって登録できる。
@ExtendWith({ FooExtension.class, BarExtension.class }) public class FooTest { @Test @ExtendWith(BazExtension.class) void qux() {} }
なお、残念なことにjunit-jupiter-apiにはデフォルトで提供されている Extension はない。したがって、JUnit4で ExternalResource とか TestName を使っていた場合は、自分で Extension を書かなければならない(まあ、誰かがそのうち作ってくれるだろうけど…)。
各インターフェースの詳細
ContainerExecutionCondition/TestExecutionCondition
テストコンテナ(テストクラス)/テスト(テストメソッド)の条件に基づき実行制御を判断するメソッドを提供するインターフェース。それぞれ ContainerExecutionContext および TestExtensionContext を引数にとって、 ConditionEvaluationResult を返す。 ContainerEvaluationResult はstaticメソッド enabled(String)(テストを実行する)または disabled(テストを実行しない)によって生成できるオブジェクト。アノテーション @Disabled によってテストの実行制御を行っているのも、このインターフェースを実装した DisabledConditionによる。
サンプルコード : 指定した曜日しか動かないテスト
テスト中のサービスの起動状態によって、モックを使ったテストを実行する/テスト中のサービスでテストを実行するなどの切り替えに使えるかもしれない(それなら Condition でやるよりも ParameterResolver の方がよいけど…)。
各 Callback インターフェース
テストの各種フェーズをhookにして起動できる処理を定義する。起動される順序は上述のとおり。またJUnit5のドキュメントに書かれているようにテストの実行時間を計測したい場合は、テストクラスの @BeforeEach/@AfterEach ではなく @BeforeTestExecutionCallback および @AfterTestExecutionCallback によって計測するほうがより正確な実行時間が計測できる。
例外ハンドリング
TestExecutionExceptionHandler によって例外発生時の処理を挟み込むことができる。まあ、特定の例外は無視するくらいの使いみちしかないと思われるが…
TestInstancePostProcessor
テストクラスのインスタンスを起動した後に、特定の処理をおこないたい場合やDI、モックの準備をしたい場合などに作る。
サンプルコード
@PostConstruct アノテーションが付与されているメソッドを起動する TestInstancePostProcessor
public class PostProcessor implements TestInstancePostProcessor { private static final Predicate<Method> annotatedWithPostConstruct = m -> m.isAnnotationPresent(PostConstruct.class); @NotNull private static Consumer<Method> invokePostConstruct( @NotNull Object o ) { return m -> { try { m.setAccessible(true); m.invoke(o); } catch (IllegalAccessException | InvocationTargetException e) { final String name = m.getName(); throw new IllegalStateException(name + " method cannot be invoked.", e); } }; } @Override public void postProcessTestInstance( Object test , ExtensionContext context ) throws Exception { Stream.of(test.getClass().getDeclaredMethods()) .filter(annotatedWithPostConstruct) .findAny() .ifPresent(invokePostConstruct(test)); } }
サンプルコード続きはこちら
ParameterResolver
ParameterResolver はテストメソッドの引数を解決するインターフェース。 support メソッドで引数を渡すことができるか判断し、引数を渡せる場合に resolve メソッドで実際の引数を渡す。メソッドに引数を渡せると聞くと、それ自身がテストのassertionの対象となる値を渡せると捉えがちだが、どちらかというとテストに関するメタ情報や特別な処理が必要になるオブジェクト(DI管理のオブジェクトやDIの対象となるオブジェクト、モックオブジェクトなど)を引数にとるようにする。(テストのためのパラメーターについては、 DynamicTest で作ればよく、テストのメソッドの引数にはそのパラメーターを作るためのオブジェクト(例えば File など)を渡した方がよい)。
サンプルコード
@InputString アノテーションを付与した String 型の引数に @InputString のパラメーターで指定した文字列を渡す。