GroovyでJUnitなテストを書くときの注意点……なんて無かった #gadvent2012
Groovy!!(挨拶)
G* Advent Calendar 2012 の13日目でございます。
- 前日: SpockでビルトインされているExtensionsとかそのへん - Yamkazu's Blog / @yamkazuさん
- 翌日:
groovy.lang.MissingPropertyException*1*2
JUnitをGroovyで使うなど
何も考えずに突っ込みどころ満載のFizzBuzzとテスト(色々足りてない)を書きます。
これをGroovyで書くとします。
- HogeGroovyTest.groovy(ほぼ HogeJavaTest.java のまま)
そのままJUnitとして動きます。Eclipse と IntelliJ IDEA では動くの確認できてます。
まんまコピペで何も嬉しくないので、Groovyらしくセミコロンを消したり、要らない括弧も消しましょう。
assertThat(sut.fizzBuzz(3), is("Fizz")); // Java assertThat sut.fizzBuzz(3), is("Fizz") // Groovy!!
素晴らしい!みんなGroovyやりたくなったね!
……んなバカな。
プロダクトコードをJavaで書いてテストをGroovyで書くアプローチはとても有効だと思ってるんですが、先に挙げたようなところで止まると微妙。いや止まらないとは思うのですが。
JUnitをそのまま使えるので、それなりのJUnit力が有れば特に問題は無いと思います。とは言っても、「何か使えなかったり、ハマる所有るんじゃないの?」とか心配する気持ちもわかります。わかりませんけどわかることにします。なので一通りやってハマりどころを書こうと思いました。
……すんなり出来すぎて*3何も出て来なかった。あえて言うなら「中途半端にGroovyにかぶれてると困るかもしれない」とか。以下はそんなメモです。
JUnitに関わるあれそれ
ごくごく普通に @RunWith とか @Rule とかも使えます。
RunWith がちょっとシンプルに書ける
ちょっとだけね。
@RunWith(Enclosed.class) // Java @RunWith(Enclosed) // Groovy
Rule はちゃんと書かないと怒られる
@Rule public TestRule watcher = new TestWatcher() { @Override protected void starting(Description description) { println description.methodName } }
折角Groovy使ってるんだから、色々と手を抜きたいと思うものです。でも上記コードの public TestRule を def とか書いたらダメーって言われる。JUnitのエラーメッセージは親切なので、こけたらわかるんですが、一応書いておきます。
JUnitが気にしてるのは @Rule がついてるフィールドが public な TestRule であることでしかないので、そこに何を入れるかは好きにして構いません。なので、上記コードはこんなふうに書くことも出来ます。誰得。
@Rule public TestRule watcher = [starting: { println it.methodName }] as TestWatcher
Thories もすんなり
@RunWith(Theories) static class TheoriesTest { HogeGroovy sut = new HogeGroovy() @DataPoints static Object[] params() { def map = [:] map << [3: "Fizz", 12: "Fizz"] map << [5: "Buzz", 10: "Buzz"] map << [15: "FizzBuzz", 30: "FizzBuzz"] } @Theory public void test(def param) { assert sut.fizzBuzz(param.key) == param.value } }
あえて言うならば @Datapoints の型は配列でなきゃヤダーと言われるところですかね。めんどくさいんで Object[] にしちゃってますが、害は無いでしょう。あまり意識してなかったのですが、ここでは map が Object[] に強制型変換食らって Entry 各々の配列になってたりします。動いてびっくりした……。
Spockを混ぜ込む
ここまで来たら、みんな大好き Spock を混ぜ込みましょう。おもむろにEnclosedランナーのクラスにネストさせるだけです。
@RunWith(Enclosed) class HogeGroovyTest { // ...略... static class HogeSpock extends Specification { def "Enclosedの中でSpockが使えるよー"() { expect: true } } }
嬉しいですね。とても嬉しいですね。こういうことが出来るのも、SpockがJUnitだから……Spockを使うときに継承する Specification が @RunWith(Sputnik.class) が付けられたクラスだったりするんです。つまりSpockは元々JUnitなので、JUnitと混ぜて自然に実行できても何ら不思議ではないんです。
Spockが使える状況下でJUnitを使うのかは正直わかりませんが、それぞれ良さがあると思うんですよ。なのでその気になればこんな風に書けるってのは、場合によっては便利。かも。JUnitなのでSpockにもRuleが使えたりします。有効な場面には今の所遭遇したことないですが。