同じテストロジックで、入力値のバリエーションテストを行いたい場合、入力値と期待値をパラメータ化したテストケースが書けることを知ったので使ってみた。
参考: JUnit実践入門
JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)
- 作者: 渡辺修司
- 出版社/メーカー: 技術評論社
- 発売日: 2012/11/21
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 273回
- この商品を含むブログ (68件) を見る
以下、テストケースのサンプル。
package junit.theory; import org.junit.experimental.theories.DataPoints; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.runner.RunWith; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertThat; @RunWith(Theories.class) public class TheorySampleTest { // テスト条件を設定したクラス public static class Fixture { int a, b; int result; public Fixture(int a, int b, int result) { this.a = a; this.b = b; this.result = result; } @Override public String toString() { return "Fixture{" + "a=" + a + ", b=" + b + ", result=" + result + '}'; } } // テスト条件 @DataPoints public static Fixture fixtures[] = { new Fixture(1,2,3), new Fixture(2,2,4), new Fixture(2,3,4), //wrong new Fixture(0,1,2), //wrong new Fixture(1,1,3), //wrong new Fixture(2,4,6), }; // テスト @Theory public void testAdd(Fixture f) { assertThat(f.a + f.b, is(f.result)); System.out.println("execute:" + f); } }
ポイントは、
- テストランナーとして@RunWith(Theories.class)を指定。
- テスト条件(入力値と期待値)のクラスを作成する。なお、上記サンプルのtoString()はなくてよい。
- 上記テスト条件クラスの配列を作成する。この配列が試験バリエーションそのものになる。この配列はpublic staticな配列とし、@DataPointsアノテーションを付与する。
- テストメソッドには@Theoryアノテーション(@Testは使わない)を付与し、テスト条件のクラスを引数で指定する形とする。テストメソッドは、引数で渡された条件を使用したテストとして実装する。
このサンプルでは使ってないけど、RunWith(Theories.class)のクラス内でも@Beforeとか、@BeforeClassとかのアノテーションメソッドは問題なく使用できる。
なお、テスト条件については、外部ファイルから読み込むことも出来るらしいが試してない。
パラメータバリエーションのテストでパラメータだけ違うような同じロジックを延々と書かなくて良いので便利なんだけど、弱点が2つある。
上記のテストを実行した結果。
execute:Fixture{a=1, b=2, result=3} execute:Fixture{a=2, b=2, result=4} org.junit.experimental.theories.internal.ParameterizedAssertionError: testAdd(fixtures[2]) at org.junit.experimental.theories.Theories$TheoryAnchor.reportParameterizedError(Theories.java:183) at org.junit.experimental.theories.Theories$TheoryAnchor$1$1.evaluate(Theories.java:138) at org.junit.experimental.theories.Theories$TheoryAnchor.runWithCompleteAssignment(Theories.java:119) at org.junit.experimental.theories.Theories$TheoryAnchor.runWithAssignment(Theories.java:103) at org.junit.experimental.theories.Theories$TheoryAnchor.runWithIncompleteAssignment(Theories.java:112) at org.junit.experimental.theories.Theories$TheoryAnchor.runWithAssignment(Theories.java:101) at org.junit.experimental.theories.Theories$TheoryAnchor.evaluate(Theories.java:89) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) at org.junit.runners.ParentRunner.run(ParentRunner.java:300) at org.junit.runner.JUnitCore.run(JUnitCore.java:157) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:77) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) Caused by: java.lang.AssertionError: Expected: is <4> got: <5> at org.junit.Assert.assertThat(Assert.java:780) at org.junit.Assert.assertThat(Assert.java:738) at junit.theory.TheorySampleTest.testAdd(TheorySampleTest.java:45) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42) at org.junit.experimental.theories.Theories$TheoryAnchor$2.evaluate(Theories.java:167) at org.junit.experimental.theories.Theories$TheoryAnchor$1$1.evaluate(Theories.java:133) ... 23 more Process finished with exit code -1
エラーになったパターンが"testAdd(fixtures[2])"のように出力されるので、3つ目のパターンでNGになったことが分かる。
が、3つ目でNGになったら、以降のパターンのテストは行われずに終了する。この点は残念。
もう一個の弱点は、Eclipse+Quick JUnit環境で、@TheoryアノテーションのテストケースメソッドはCtrl+0で実行出来ない。
なので、メソッド個別のテストではなく、Ctrl+F11キーでテストケース内の全テストを実行することになる。こちらはあまり大きな問題ではないかも。