日々常々

Go Ahead.

GroovyでJUnitなテストを書くときの注意点……なんて無かった #gadvent2012

Groovy!!(挨拶)
G* Advent Calendar 2012 の13日目でございます。

JUnitをGroovyで使うなど

何も考えずに突っ込みどころ満載のFizzBuzzとテスト(色々足りてない)を書きます。

これをGroovyで書くとします。

そのままJUnitとして動きます。EclipseIntelliJ 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が使えたりします。有効な場面には今の所遭遇したことないですが。

*1:まだと言う意味です。他意は有りません。

*2:ネタにされたので消せなくなったw

*3:わかっていたことではある。