@Ruleは素晴らし。
@Rule。このアノテーションは、あまり知られていないようですが、ヤバいです。このアノテーションが追加されたのは4.7からなので結構古いのです。@Ruleのうれしさは、カスタムで作られているRunnerをほぼ置き換えられる、ということを言われていました。ただ、僕はあんまり使った事がなかったですし、周りでも使っている話はあまり聞いた事がありません。でも、使ってみて非常に便利だと感じました。
例えばTemporaryFolderを利用して作成されたファイルは、テストの終了時に自動的に削除されます。ちなみにTemporaryFolderの使い方はこんな感じです。
public static class HasTempFolder { @Rule public TemporaryFolder folder= new TemporaryFolder(); @Test public void testUsingTempFolder() throws IOException { File createdFile= folder.newFile("myfile.txt"); File createdFolder= folder.newFolder("subfolder"); // ... } }
... この例はJUnitのJavadocからお借りしました:-P 要するにfolderから作ったcreatedFileやcreatedFolderはtestUsingTempFolderのテストメソッドが終了すると削除されます。テストケース毎に新しいファイル、フォルダが作られる事が保証される訳です。
で、この@Ruleですが、一つのクラスで一つだけ、という事はありません。JUnitにある@Ruleは他にTestWatchmanやTimeoutなどがあります。どれもそのテストクラス全体を通じて必要な、横断した前処理、後処理をルールとして宣言できるのです。(Runnerと比べたらRuleの方が良い名前だと思いませんか?)
@Ruleの追加も簡単です。こうしたメソッド毎の前処理や後処理を追加するルールであれば、MethodRuleを継承して実装します。MethodRuleのインタフェースを見てみると、次のメソッドが一つ定義されています。
Statement apply(Statement base, FrameworkMethod method, Object target)
第1引数のbaseは、テストメソッドが担う一連の処理を持ったインスタンスです。evaluate()を呼び出すと、テストメソッドが評価されます。第2引数のmethodはテストメソッドのモデルです。そのメソッドの名前やアノテーションを取得できます。第3引数はテストのインスタンスそのものです。
さて、返り値のStatementですが、Ruleの中で生成しても良いですし、baseをそのまま返してもいいです。ほとんどの場合、Ruleの中で生成する形式をとります。Ruleの中で生成すると、テストケース前後に処理を追加する事ができます。例えば、TestWatchmanという、テスト実行中の状況をログに出力するなどの処理を行うためのルールクラスのapplyの実装は次のようになっています。
public Statement apply(final Statement base, final FrameworkMethod method, Object target) { return new Statement() { @Override public void evaluate() throws Throwable { starting(method); try { base.evaluate(); succeeded(method); } catch (Throwable t) { failed(t, method); throw t; } finally { finished(method); } } }; }
starting()やsucceded()をオーバーライドする子クラスを実装する事で、テストメソッドの実施状況を知るためのルールを書く事ができるのです。
ではbaseをそのまま返す場合はどんな場合かというと、そのテストケースの実行自体を判断するようなルールで使えるでしょう。例えば実行しているOSにより、テストを実行するかどうか判断するようなケースです。*1
これまでカスタムでRunnerを作成するのは、テストの前処理や後処理を追加するために書かれる事が多かったと思われます。また、親のテストクラスを作って、それを継承するようなテストクラス設計をする方もいるでしょう。しかし@Ruleの追加により、もっと汎用的に、しかも複数の処理を割り込ます事ができます。っすごいっす。
*1:残念ながら、実行結果をIgnoreにするルールは作れないですが