JUnitで全テストで共通の前処理・後処理をしたい

「全テストで共通の処理をしたい」というのは結構ありました。

  • DBのクリーンアップとかセットアップをしたい
  • テスト用のHTTPサーバを立ち上げて、テストによって適当な応答を返して欲しい
  • あるログをtailしてテスト毎に出力されたログを記録して欲しい
  • 一時的にSystemプロパティを書き換えてテスト後に戻したい
  • テスト毎にGCしてメモリ取って欲しい
  • ゴミファイルは削除して欲しい

こんな感じで「単体テストですることか?」とか思うものもあったけど、対応したりしてた。

いくつか方法はあるけども、あんまり見ないので、自分がやった範囲で適当にメモしておく。知ってて当然なのかな?

  • Ruleを使う
  • 継承を使う
  • おれおれTestRunnerを書く

Ruleを使う

一番良い方法です。何が良いか。

  • クラスで書かざるを得ないので、嫌でも共通化できる
  • 書くクラスによってパラメータを変えたりできる
  • 主にフィールドに書くのでテストメソッドから参照できる
  • クラス単位・メソッド単位でRuleを実行できる(@ClassRule/@Rule)

2つ目と3つ目のおかげで、かなり柔軟です。大体なんでも出来ます。
まずはRule化で共通化できないか考えてみると良いと思います。

ただデメリットもあります。

  • 各テストクラスに書かないといけない

Ruleは書かないと効力は発揮しません。
各テストに@Ruleを追加する必要が出ると、各テストに2行程度書かないといけないのは結構しんどいです。

ただ、テスト開始時に1回だけ行えば良い、という操作であれば、テストスイート(Suite)を作って、@ClassRuleを書くのが良いと思います。
SuiteやEncloseでは@ClassRuleが使えるので。

継承を使う

JUnit3時代に戻った感じになりますが、テストクラスが膨大になることが予想できるのであれば、継承を使う方法があります。
TestCaseを継承するのではなく、適当なテスト用の抽象クラスを書き、@Ruleをそこで定義したりするだけです。

テストランナーはスーパークラスで定義された@Ruleを読み取ってくれます。なので、継承を使えばRuleを増やす事になっても継承元の1クラスの修正だけで対応できます。
Ruleはpublicで宣言する事になるので、自然とサブクラス側でも呼び出して利用する事もできます。

ただ、継承はあまり良くないです。使うとだいたい以下の問題が起きます。

  • テストメソッド中でいきなりスーパークラスのフィールドが出てきたりして混乱しやすい
  • 特定のテストでは全く使われないRuleを定義することがある
  • 継承した全テストクラスに影響を与える

簡単に言えば、読みにくくなったり、テストが遅くなったり、テストが通らなくなったりします。

おれおれTestRunnerを書く

http://stackoverflow.com/questions/9034812/how-to-apply-a-junit-rule-for-all-test-cases-in-a-suite

テスト失敗時のリトライ処理を全テストメソッドに加えたい場合の回答があります。

テストランナーでテストメソッドを呼び出す直前に、テストランナーで用意したTestRuleを適用する方法です。

事前に各テストクラスに@RunWithでこの独自Runnerを与える必要があるため、他のテストランナーが使えなくなります。
当然、テストメソッドからはそのRuleが見えないので、Ruleが持つ操作を行う事はできません。

副次的なメリットになりますが、特定のTestRuleを必ず最初に実行するように仕向ける事もできます。別にRuleChain使えばいいだけの話ですが。

おわり

基本はRuleで共通化しましょう。
継承はそのときは便利だけど、後が怖い。
オレオレTestRunnerはわりと論外。

Suiteとかで@Ruleを読み取って各テストメソッドで呼び出すぐらいの事をしてくれると良いんだがなぁ。ParentRunnerの動き的に難しそうだけども。

他に良い方法ないかなー。