前回の続き
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
は次の順番でテストクラスの生成、テストの実行、テスト終了までの一連の流れに組み込まれる。
ContainerExecutionCondition
BeforeAllCallback
- テストクラスの
@BeforeAll
- テストクラスのインスタンス生成
TestInstancePostProcessor
ここから、テストがある場合
TestExecutionCondition
BeforeEachCallback
- テストクラスの
@BeforeEach
BeforeTestExecutionCallback
ParameterResolver#supports
(引数の数だけ)ParameterResolver#resolve
(引数の数だけ)- テストクラスの
@Test
(テスト) TestExecutionExceptionHandler
(テスト中に例外が発生した場合だけ)AfterTestExecutionCallback
- テストクラスの
@AfterEach
AfterEachCallback
ここから、同じクラスに他のテストがある場合
- 次のテストのインスタンス生成
TestInstancePostProcessor
TestExecutionCondition
- ...
AfterEachCallback
ここから、ネストしたクラスがある場合
ContainerExecutionCondition
BeforeAllCallback
- アウタークラスのインスタンス生成
TestInstancePostProcessor
- インナークラスのインスタンス生成
TestInstancePostProcessor
TestExecutionCondition
BeforeEachCallback
- アウタークラスの
@BeforeEach
- インナークラスの
@BeforeEach
BeforeTestExecutionCallback
ParameterResolver#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
のパラメーターで指定した文字列を渡す。