Theoriesさんの可読性をなんとか
ということなので、いろふさん早くSpockについてブログ書いて下さい。
mike、mikeなるままに…: Spockで例外のテスト
とか言われたのでTheoriesネタで書きます。
Theoriesさん?
JUnit実践入門 8章 パラメータ化テスト をご参照下さい。
JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)
- 作者: 渡辺修司
- 出版社/メーカー: 技術評論社
- 発売日: 2012/11/21
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 273回
- この商品を含むブログ (68件) を見る
……ざっくり言うと、パラメータ化テストは「一つのテストメソッドにパラメータを与えて複数のテストをするもの」です。テストメソッドの引数に入ります。色々方法はあるんですけど、シンプルな形だとこうなります。
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.is; import static org.junit.Assert.assertThat; @RunWith(Theories.class) public class TheoriesSample { @DataPoints public static String[] params = {"hoge", "fuga", "piyo"}; @Theory public void theory(String param) { assertThat(param.length(), is(4)); } }
これで3ケースのテストがされます。JUnitのレポートには1ケースとして出るので、すこしわかり辛いですが。でもこれだと期待値が固定になってしまうため、パラメータ化の嬉しさがあまりありません。なので期待値もパラメータで渡しましょう。
こうでしょうか?
@DataPoints public static String[] params = {"hoge", "fuga", "piyo", "foo"}; @DataPoints public static int[] lengths = {4, 4, 4, 3}; @Theory public void theory(String param, int length) { assertThat(param.length(), is(length)); }
残念ながら、これだとうまくいきません。Theoriesはパラメータに入ってくるものを全部組み合わせてくれます。つまり4*4の16パターンやってくれるんですね。そして param="foo", length=3 のテストがコケちゃいます。
……この組み合わせ、正直私は使いこなせてないです。なんか直感的じゃないし、何が嬉しいかわかんない(´・ω・`)
Theoriesさんの書き方について
パラメータを複数扱うと途端にTheoriesさんは私の制御下を離れるので、パラメータオブジェクトを作って、それであれをそれします。mike_neckさんはこんな感じで書かれてますね。
@DataPoints public static Regulations[] regulations = Regulations.values(); @Theory public void exceptionThrownValidly (Regulations regulation) { // 以下略
個人的にはイケてない気がする…
mike、mikeなるままに…: Spockで例外のテスト
同感です。でも配列が問題と言うわけでもないと思います。*1
ちょっとC#に脱線
ところでC#のNUnitさんはTestCase属性(Javaで言うアノテーション)を使ってパラメータ化できます。結構見やすくて好きだったりします。
[TestCase(1, "1")] [TestCase(3, "Fizz")] [TestCase(5, "Buzz")] public void TestFizzBuzz(int input, string expected) { // 以下略
ぐるぐるさんが3年弱前にさくっと通りすぎてた道でした。HAHAHA。
TestCaseSource属性なんてあったのか……これが若干 @DataPoints に近く感じました。
DataPointsの可読性にこだわる
本題。@DataPoints はここまでの例ではフィールドにつけていましたが、別にフィールドでなくメソッドにも付けられます。 public static T[] となっていれば何でもいい感じです。どうでもいいですが、フィールドだとIDEが「未使用フィールドだ!」とか言ってキレたりするので、メソッドにしとくと心の平穏を保てるかもしれません。
パラメータオブジェクトを作るくらいなんだから、パラメータを書きやすくしたいものですよね。最初はこんな感じでしょうか。
@DataPoints public static Fixture[] params = { new Fixture(1, "1"), new Fixture(3, "Fizz"), new Fixture(5, "Buzz"), };
new Fixtureとか書くのが面倒なので _ メソッドとか作ります。
@DataPoints public static Fixture[] params = { _(1, "1"), _(3, "Fizz"), _(5, "Buzz"), }; static Fixture _(int param, String expected) { return new Fixture(param, expected); }
……まずい、例が悪かった。もうこれでいいやとか思ってしまった。
いいや、このままいこう。配列嫌いの方はList使っても良いと思います。
@DataPoints public static Fixture[] params() { List<Fixture> list = new ArrayList<>(); list.add(_(1, "1")); list.add(_(3, "Fizz")); list.add(_(5, "Buzz")); return list.toArray(new Fixture[list.size()]); }
Listでいいならこんな風にも書けるけど、うん、どうなんだろう。
@DataPoints public static Fixture[] params = new ArrayList<Fixture>() {{ add(_(1, "1")); add(_(3, "Fizz")); add(_(5, "Buzz")); }}.toArray(new Fixture[0]);
……って感じで記述量を減らしたりとかやってると、何でも出来るってことに気づきます。
どうせやるなら
パラメタライズドテストではデータが主体であって、そこだけを見て、そこだけを編集すればデータが追加できて、こけたときもそこを見て済むのが良いと思ってます。なので、こんな解答もありじゃないかなーと思ってます。
@DataPoints public static Fixture[] parans = new Builder() {{ when(1).then("1"); when(3).then("Fizz"); when(5).then("Buzz"); when(0).thrown(IllegalArgumentException.class); }}.build();
パラメータの数が増えたりしてくると、こっちはかなり威力を発揮してきます。実際業務でも使ってて、私一人だけど、それなりに快適です。
(求むツッコミ!)らしいので……
- 例外を ExpectedExceptionルール を使う。
- DataPoints を上記のの雰囲気で書いてみる。
ざっくり書いたら https://gist.github.com/4397706#file-executionpatternstestbyjava-java になりました。
まとめ
すなおにSpock使え。
いやまじで。アレコレやっても https://gist.github.com/4397706#file-executionpatternstestbygroovy-groovy には敵いそうもありません。
*1:やるにしても「配列とIterable」くらいにしかならないでしょうし。