White Box技術部

WEB開発のあれこれ(と何か)

GitHub Kaigiからの~Grails 2.3系にまつわるSpockやJUnitの話

※コメントを受けてSpock周りの話を修正しました(2014/06/23)

GitHub Kaigi

先週GitHub Kaigiに行ってきました。 去年のLLまつり以来の勉強会ちっくなイベント参加だったのでかなり期待していましたし、 実際期待以上に面白かったです。
Rebuildの公開録音聴いてるときの家に居る感はすごかったw

特にはてなブログの開発フローの話とか、バグ管理システムを作ってる身としては色々思うところがあって面白かったです。
(受託開発的にはホワイトボードだけで管理される開発中の情報があると、後でお客さんの調査部門に怒られるかなーとか)

いやー他にも色々話を聞いて、バグ管理システム作るの止めようかとも思ったのですが、 作ろうと思ったきっかけの理由はGitHub系のシステムじゃ満たしてくれないので、 利用フローをちゃんと提示することを忘れずに、このまま作ってみようと思います。

あとは英語がわかればもっと面白かったに違いなくて、なんとか英語力を鍛えねばと想いを新たにしたり、 主催メンバーの伊藤さんの振る舞い・気配りがすごく適切で、話の内容とは別に勉強させて貰ってました。

あえて不満を言うなら、みんな楽しそうな仕事してるんだなぁと相当羨ましくて辛かったぐらいですからねw
こちらにレポートがあるので、ぜひ雰囲気を感じてみてください。

去年はLLまつりの後、IETF報告会にも行ってみたのですが、 周りは仕事で、私は有給で、みたいな感じだったのと、身内ネタというか身内での報告会みたいな雰囲気があって、 外部参加であるところの自分はアウェー感が強かったです。話自体は面白かったのですが、あれはもう行かないかなぁ。

しかしISOC-JPWiki、恐ろしくリンクが辿り辛いです。 SeasarプロジェクトのHPも相当でしたが、ISOC-JPと比べると段違いですね。 会員募集!とかIETFへの日本人の参加者がもっと増えて欲しい!とか本当に思っているのであれば、入り口をもっと整えた方がいいのではないかと個人的には思いますが、どうなんですかね?

そういえばちょうど前日にGit 2.0がリリースされてましたが、GitHubの対応はどうなるのでしょうか。今のところ特に周知もないようですし。
Gitをあんまり使い込めてない自分としてはどうということはないんですが、後方互換消えてるものもあるようなので、 GitHubの対応が終わるまでローカルのアップデートは控えようかなと思っています。

GrailsのUnitテスト

さて、本題に入りますか。
GitHub KaigiのどこかのコマでPower AssertとGroovyの話が出てきて、 「Power Assert単体を話題にするより、Spockを話題にした方がいいんじゃないっけ?」と思ったのと、 GrailsでSpockに詰まった話をしてなかったことを思い出したので、今日はGrailsのUnitテストについて話そうと思います。

表題

  • Spock rather than Power Assert
  • GrailsでSpockのテストを実施する
  • Grails 2.3系でもSpockじゃなくてJUnitをUnitテストのデフォルトにできるのか?(別の機会で)

どうせならPower AssertじゃなくてSpockを使おうよ!

Groovyに含まれるPower Assertは、assertに失敗するとその時の各パラメータの情報をわかりやすくログに出力してくれる機能で、 エラー原因の特定を助けてくれる便利な機能です。 このPower Assert、元はTesting FWであるSpockの機能で、それがGroovy 1.7から有用性が認められてGroovy本体に取り込まれた、という経緯を持っています。

ということは良いと言われるPower Assertを元から持っているSpockを使うと、より幸せになれるんじゃないかと思うわけです。

あまり詳しくないので簡単にですが、例えばSpockを使った場合、 assert失敗時のパラメータ情報に加え、どこがどの程度違っているかを表示してくれます(サンプルは後述)。

GrailsでSpockを使おう

Grailsは2.3系からそんなSpockをデフォルトのTesting FWとして採用しています。

そしてそれに気付かず、なんでこのJUnitで書いたテスト動かないんだ?と四苦八苦していた間抜けが私です、はい。

2.4系が出たタイミングで2.3系の話をするのも今更感がありますが、2.3系の話は以下のサイトがわかりやすいと思います。

Spockの書き方については以下のサイトを参考にさせて貰いました。

SpockでDB値を確認

作っているシステム的に、DB値確認をするテストが書きたかったのですが、これが結構詰まったので以下にサンプルを残しておきます。 ※bug.saveの周りにあるwithTransactionのブロックは自分用のメモのため、実際には不要です。flushもfalseでも動作します。

@TestFor(Bug)
class BugSpec extends Specification {

    def インスタンス生成確認() {
     setup:
        def bug = new Bug(title:"first bug", bugInfo:"bug information")
        Bug.withTransaction {
            bug.save(flush: true, failOnError:true)
        }

        def somebug = Bug.get(bug.id)                // ここでのbug.idの値は1
        println somebug.toString()

//     assertEquals("first bug", somebug.title)     // JUnitの書き方でもテストはできる(が結果出力もJUnitと同じ)
//     assert "bug information" == somebug.bugInfo  // expect以外の個所でもチェックはできる(がここや↑の評価で失敗するとexpectの評価はされない)


     expect: somebug.getProperty(name) == checkvalue    // somebug.getProperty(name)はsomebug."$name"と書く方がgroovyっぽいかと
     where:
        name | checkvalue
        "title" | 'first bug?'
        "bugInfo" | 'bug information'
//     somebug.bugInfo | 'bug information'          // whereの中でテストメソッドのローカル変数は参照できない

    }
}

結果はこんな感じで表示されます。

インスタンス生成確認(white.box.palpunte.BugSpec)
Condition not satisfied:

somebug.getProperty(name) == checkvalue
|       |           |     |  |
|       first bug   title |  first bug?
|                         false
|                         1 difference (90% similarity)
|                         first bug(-)
|                         first bug(?)
white.box.palpunte.Bug : 1


    at white.box.palpunte.BugSpec.インスタンス生成確認(BugSpec.groovy:37)

expectのsomebug.getProperty(name)、またはsomebug."$name"とするのがポイントです。これになかなか気付けなかった。。
expectをname == checkvalueとして、whereをsomebug.bugInfo | 'bug information'のようにはするのはダメのようでした。

コメントで指摘いただきましたが、whereブロックで宣言したデータテーブルは、 テスト実行時に暗黙的にテストメソッドの引数として扱われる、ということで、 まだないテストメソッドのローカル変数の値を参照できるわけがないんでしょうね。そりゃそうですね。

ちなみにbugInfoの方は文字列が一致するので今はエラーになりませんが、 これも不一致するようにしておくと、titleとbugInfo両方で失敗したことが通知されます。 JUnitでこれをやるにはもうちょっと大変なので、この点でもSpock楽だなーと思いました。

あとメソッド名が日本語なのはJUnit実践入門の影響です。どういったテストが失敗したのかが一目でわかるのでテストのメソッド名を日本語にするのはお奨めです。

以下はEclipseで実行した場合のイメージです。
Eclipseで開発している場合は、(実際はSpockなので紛らわしいですが)JUnitの実行構成を作ることでテストクラス単体の実施が簡単にできます。

f:id:seri_wb:20140608213439p:plain

解像度が上がらないので見辛くて申し訳ないのですが、エラーがわかりやすいのをイメージして貰えるかと思います。
どうですか?Spock、良さそうじゃないですか?


疲れたので「Grails 2.3系でもSpockじゃなくてJUnitをUnitテストのデフォルトにできるのか?」は別の機会に回すことにします(機会があるかはまた別の問題ですが)。

簡単に結論だけ書くと、設定でどうにかすることはできないので、Grailsのテンプレートを書き換える必要がありそうです。

grails-2.3.7\src\grails\templates\testing\UnitTest.groovy

動作確認はしてませんがコードを読んだ感じ、このファイルをテンプレートにしてるようなので、これ自体をJUnitちっくに書き直すことで JUnitをデフォルトにすることができそうでした。

ですが、Spockは結構なポテンシャルを秘めているように感じるので、Grailsを使う場合はぜひSpockでテストを書いてみてください。 そしてその情報をください!もとい共有しあいましょう!