JUnitでプラットフォーム依存のテストを行う

JUnitでプラットフォーム依存のテストを行いたい*1とき、みなさんはどうされているのでしょうか。テストメソッド内で System.getProperty("os.name") 等としてOS判定し、実行する処理を分岐させたりしているのでしょうか。
こんな感じで、アノテーションで実行を制御できたら楽ですね。


Junitでは、特定の条件下でのみそのテストを走らせる、といったオプションがありません。あるテストスイートを実行しないようにする @Ignore アノテーションはありますが、これはテスト実行の条件を指定して特定条件下でのみ無視するといったものではないため、今回のケースには適しません。

さて、そこはプログラマーの発想であるところの「ないなら作ればいいじゃない」ということで、実装してみましょう。まず、アノテーション @DependsOnPlatform を作ります。引数でプラットフォームを指定できるようにしましょう。Platform 型は enum で、プラットフォームを指定するための型であるとします。



次に、JUnit側をカスタマイズします。目標は以下です。

テストスイートの実行時に、テストスイートに @DependsOnPlatform アノテーションが与えられていたら、実行環境のプラットフォームを取得し、アノテーションの引数で指定されている値と一致するか調べる。一致した場合のみテストを実行する。


まず、テストスイートをリフレクションで実行するBlockJUnit4ClassRunner のソースを見てみます(以下は一部抜粋)。


見てのとおり、@Ignored アノテーションがついている場合は無視、そうでない場合は実行、となっています。そして、肝心の runNotIgnored メソッドは private 宣言されています。これは runChild メソッドに手を入れるしかなさそうです。BlockJunitClassRunner を継承した CustomJUnit4ClassRunner を作成し、runChild メソッドをオーバーライドします。


@DependsOnPlatform アノテーションの内容をチェックして、実行条件を満たしていれば runNotIgnored を、そうでなければ runIgnored を呼び出すようにしています。runNotIgnored や runIgnored メソッドは private 宣言されており super できませんので、親クラスからそのまま持ってきてコピペしました。
これで、最初のテストが無事プラットフォーム別に実行できるようになりました。利用するときは、最初の例にもあるとおり @RunWith(CustomJUnitClassRunner.class) を宣言する必要がある点に注意してください。なお、プラットフォームの判定については大ざっぱもいいところですので、もっと細かく制御したい方は上記内容を参考にさらなるカスタマイズをしてみてください。


最後に。今回手を入れた JUnit のバージョンは 4.8.2 となります*2。さきほど trunk のソースを見にいったところ、今回手を入れている部分のソースが若干変わっていました*3


追記:さっそく @cactusman さんからツッコミをいただきました。

どうやら JUnit 4.8 から新たに @Category というアノテーションが導入されているようです。このアノテーションはテストの実行条件を細かく制御する目的で導入されており、このアノテーションを利用することで今回の当初の目的を達成することもできそうです。Thanks!

*1:そもそも、原則としてプラットフォームを意識しないでよいはずのJVMを使っている以上、そのような状況自体がレアだというツッコミはあるでしょうが。

*2:https://github.com/KentBeck/junit/blob/r4.8.2/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java

*3:https://github.com/KentBeck/junit/blob/master/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java