G* Advent Calendar 2013 1日目 - Gaiden

この記事は毎年恒例の G* Advent Calendar 2013 の1日目の記事です。1日目担当のyamakzuです。こんにちは。

今日は、先月10月頭にリリースしたGaidenについて紹介します。

GaidenはGroovy製の軽量ドキュメンテーションツールです。 この手のドキュメンテーションツールではSphinxやAsciiDocが有名ですが、ちょっとしたドキュメントを記述するには少し大きすぎるツールに感じるときがあります。

Gaidenはもっと簡単に気軽にドキュメントを記述できるようなツールを目指しています。 シンタックスにはMarkdownを採用しています。 Markdownでさっと書いて、ドキュメントを生成できる。そんなツールです。

とかいう表向きのセールストークはありますが、正直なところはGroovy使いとしてG製なツールが欲しかっただけです。

開発は主に@gantawitterと私、そしてid:nobeansのサポートのもと進めています。また、ドキュメントテンプレートの作成にid:labduckに協力してもらっています。

では早速使ってみましょう。

インストール

Groovy界隈ではお馴染みのGVMからインストールできます。

$ gvm install gaiden

WindowsではGVMが使えないのでバイナリをダウンロードして、展開したディレクトリのbinディレクトリにPATHを通してください。

プロジェクトの作成

まず始めにドキュメントのプロジェクトを作る必要があります。 create-projectコマンドで生成します。

$ gaiden create-project mydoc
$ cd mydoc

プロジェクトの構成は次のようになっています。

$ tree
mydoc
├── GaidenConfig.groovy    : Gaidenの設定ファイル
├── pages                  : Markdownで書かれたページを格納するディレクトリ
│   ├── index.md
│   └── toc.groovy         : 目次を定義するファイル
├── static                 : CSS、JSなどの静的ファイルを格納するディレクトリ
│   ├── ...
└── templates
    └── layout.html        : ページの雛形となるHTMLファイル

ページを記述する

ページはpagesディレクトリ配下にファイル名.mdというファイル名でMarkdownで記述します。 試しにpages/mypage.mdを作り、次のように記述します。

# はじめてのGaiden

Gaidenやばい   

ドキュメントを生成する

ページを記述したらドキュメントを生成してみましょう。 ドキュメントを生成するにはbuildコマンドを実行します。

$ gaiden build

ドキュメントはbuildディレクトリに生成されます。 build/mypage.htmlをブラウザで開いてみると次のような画面が生成されているはずです。

f:id:yamkazu:20131129152118p:plain

目次を作る

目次はpages/toc.groovyに記述します。 次のように記述します。

"index.md"(title: "はじめに") {
    "mypage.md"(title: "はじめてのGaiden")
} 

もう一度gaiden buildを実行してbuild/toc.htmlを開いてみてください。 次のようなページが表示されるはずです。

f:id:yamkazu:20131129152152p:plain

このtoc.groovyはGroovyのDSLで記述します。 ページ名の文字列に続けて引数にtitleという属性を設定していきます。 階層構造を持つ場合はクロージャ{}を使って、ブロック内でページを指定します。

もう少し複雑な例では次ようになります。

"introduction.md"(title: "はじめに") {
    "introduction/whatis.md"(title: "Gaidenとはなにか")
    "introduction/install.md"(title: "インストール")
}
"quickstart/quickstart.md"(title: "クイックスタート") {
    "quickstart/addingcontent.md"(title: "ページの生成")
} 

テンプレートを編集する

ページのデザインを変更したい場合はtemplates/layout.htmlを編集します。 Gaiden 0.3ではデフォルトで次のようなテンプレートになっています。

<html>
<head>
    <meta charset="UTF-8">
    <title>$title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="${resource('/css/bootstrap.css')}">
    <link rel="stylesheet" href="${resource('/css/prettify.css')}">
    <link rel="stylesheet" href="${resource('/css/style.css')}">
</head>
<body>
    <div class="navbar navbar-static-top">
        <div class="container">
            <a class="navbar-brand" href="$tocPath">$title</a>
            <a class="toc btn btn-primary btn-lg" href="$tocPath"><span class="glyphicon glyphicon-list"></span></a>
        </div>
    </div>
    <div class="container">
        <section class="page-content">
            $content
        </section>
    </div>
    <footer id="footer" class="footer">
        <div class="container">
            <p class="credit text-muted">Powered by <a href="#">Gaiden</a>.</p>
        </div>
    </footer>

    <script src="${resource('/js/jquery-1.10.2.min.js')}"></script>
    <script src="${resource('/js/bootstrap.js')}"></script>
    <script src="${resource('/js/prettify.js')}"></script>
    <script src="${resource('/js/application.js')}"></script>
</body>
</html>

テンプレートの中では${resource(..)}$content$title$tocPathといった拡張変数、メソッドが使えるようになっています。 詳しくはリファレンスを参照してください。 このHTMLを変更することで自由にデザインを変更できます。

今後について

まだまだ機能が少ない状況ですが少しずつ改善していく予定です。

年内には0.4をリリース予定です。 是非使ってフィードバックをください!

明日からは...

なんと3日間連続できょんくんです!どうしてそうなった! ではでは、よろしくお願いしますー

JJUG CCC 2013 FallのJVM言語パネルディスカッションにGroovy代表として参加してきた

あれ、気がつけば半年ぐらいブログ書いてなかった...

ということでJJUG CCC 2013 FallのJVM言語パネルディスカッションにGroovy代表として参加してきました。 いろいろあってJVM言語パネルディスカッションのGroovy代表として声をかけてもらったのですが、 Groovyについて語る自信があまりなかったのでid:nobeansに一緒に参加してもらいました。ありがとうございます!

緊張しまくって自分の資料説明するのが精一杯で、ほとんどディスカッションらしいことはできなかった気がしますが、良い経験になりました。

次は一人で参加できるように頑張ります(?)

GrailsのSpring Security Core Pluginでパスワードのソルトとストレッチング

パスワードをハッシュで保存して置くのは当たり前ですが、レインボーテーブル使用した総当たり探索の対策として、ソルトとストレッチングを組み合わせ、より安全にパスワードを保存するのが一般的になってきました。

GrailsSpring Security Core PluginはデフォルトではSHA-256のハッシュでパスワードを保存しますが、ソルトやストレッチングは使用されていません。しかし、そこは認証のデパートSpring Security、ソルトとストレッチングのサポートが組み込まれています。

まずはソルトのサポートから見て行きましょう。

usernameをソルトとする

usernameをソルトとして扱うにはConfig.groovyに以下の設定を追加します。

grails.plugins.springsecurity.dao.reflectionSaltSourceProperty = 'username'

これで認証時に、usernameをソルトとしてパスワードに付与しハッシュを計算するようになります。ただし、この設定はあくまで認証時の設定であり、ユーザのパスワードを保存する時にソルトが付与されるわけではありません。

もし、s2-quickstartコマンドを使って生成したユーザドメインクラスを使用している場合は、このドメインクラスに手を入れます。デフォルトでは以下のようにパスワードがエンコードされています。

protected void encodePassword() {
    password = springSecurityService.encodePassword(password)
}

これを以下のように変更します。

protected void encodePassword() {
    password = springSecurityService.encodePassword(password, username)
}

この様にspringSecurityService#encodePasswordの第2引数にソルトとしてusernameを指定します。

これでパスワード保存時にusernameをソルトとして付与してハッシュが計算されます。

ソルト専用のプロパティを使う

徳丸先生の資料によるとソルトには一定の長さが必要です。usernameとpasswordの組み合わせで一定以上の長さが確保できない場合は、一定の長さをもつソルト専用のプロパティが欲しくなります。

以下のようにユーザドメインクラスを変更します。

import org.apache.commons.lang.RandomStringUtils

class User {

    ...

    String salt

    String getSalt() {
        // 新規作成時にsaltがない場合にsaltを生成
        if (!salt) {
            // ランダムである必要ないが一定の長さをもつ文字列として
            // ランダムが扱いやすいのでRandomStringUtilsを使用
            salt = RandomStringUtils.randomAlphanumeric(20) // ランダムな20文字
        }
        return salt
    }

    static constraints = {
        ...
        salt blank: false
    }

    static mapping = {
        ...
        // 安全のためinsert以外でsaltが更新されないようにする
        salt updateable: false
    }

    protected void encodePassword() {
        // usernameをsaltに変更
        password = springSecurityService.encodePassword(password, salt)
    }
}

合わせてConfig.groovyreflectionSaltSourcePropertyも変更しましょう。

grails.plugins.springsecurity.dao.reflectionSaltSourceProperty = 'salt'

これで設定完了!と言いたいところですが、まだいくつかの準備が必要です。

上記のreflectionSaltSourcePropertyはSpringSecurityのUserDetailsを実装したクラスのプロパティを指定しています。GrailsのSpring Security Core PluginのデフォルトではUserDetailsの実装クラスにGrailsUserが使われます。 しかし、GrailsUserにはsaltというプロパティは存在しないため、新たにクラスを用意する必要があります。

また、このUserDetailsインスタンスUserDetailsServiceの実装クラスで生成されます。GrailsのSpring Security Core Pluginでは、これがGormUserDetailsServiceというクラスになります。

つまり独自のUserDetailsServiceを作成し、saltというプロパティを持つ独自のUserDetailsを生成する必要があります。

クラスはsrc/groovyなどに作成すると良いでしょう。

まずはUserDetailsの実装クラスを以下のよう作成します。

import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser
import org.springframework.security.core.userdetails.UserDetails

class MyUser implements UserDetails {

    @Delegate
    GrailsUser grailsUser

    String salt
}

次に上記のMyUserクラスを生成するUserDetailsServiceを作ります。既存のGormUserDetailsServiceを継承して作るがカンタンです。

import org.codehaus.groovy.grails.plugins.springsecurity.GormUserDetailsService
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.UserDetails

class MyUserDetailsService extends GormUserDetailsService {

    @Override
    protected UserDetails createUserDetails(user, Collection<GrantedAuthority> authorities) {
        new MyUser(grailsUser: super.createUserDetails(user, authorities), salt: user.salt)
    }
}

groovyの@Delegateを使用したお手軽実装にしてみました。

あとはこのサービスをSpringのビーンとして登録します。resource.groovyに以下のように追加してください。

beans = {
    ...
    userDetailsService(MyUserDetailsService) {
        grailsApplication = ref('grailsApplication')
    }
}

これでソルト専用のプロパティを使うことができます。

ストレッチング

GrailsのSpring Security Core PluginのデフォルトではpasswordEncoderorg.springframework.security.authentication.encoding.MessageDigestPasswordEncoderが使われています。

このクラスはストレッチングをサポートしています。 しかし、プラグインの設定ではgrails.plugins.springsecurity.password.algorithmというハッシュアルゴリズムの設定があるだけで、ストレッチング回数の設定はありません(デフォルトでは1回です)。

自分で新たにビーンを定義することもできますが、ここはGrailsが持つ、設定ファイルからSpringビーンのプロパティを上書きする仕組みを活用します。

Config.groovyに以下の設定を追加します。

beans.passwordEncoder.iterations = 1000

これで1000回ストレッチングします。

テスト

ソルトとストレッチングが機能しているか不安になるのでテストを書いてみましょう。

def "ソルトとストレッチングの確認"() {
    setup:
    def password = "password"
    def user = new User(username: "test", password: password, enabled: true)

    and: "パスワードにランダム文字列をソルトとして付与"
    def digest = "$password{$user.salt}".getBytes("UTF-8")

    and: "1000回ストレッチング"
    1000.times { digest = MessageDigest.getInstance("SHA-256").digest(digest) }
    def hashedPassword = new String(Hex.encode(digest))

    when: "新規Userを保存"
    user.save()

    then: "ソルトとストレッチングを使用したハッシュになっていること"
    user.password == hashedPassword
}

うまく動いているようです。

おわりに

ということでソルトとストレッチングで安全にパスワードを保存しましょう。 プラグインのリファレンスは以下を詳しく見ておくとよいです。

http://grails-plugins.github.io/grails-spring-security-core/docs/manual/guide/12%20Password%20and%20Account%20Protection.html

なお、この記事の内容は自己責任でご利用ください。ではでは。

この記事はGrails2.2.1、Spring Security Core Plugin1.2.7.3をもとに記述しています。

G*ワークショップZ May 2013 - Spockハンズオン

でしゃべってきました。

http://jggug.doorkeeper.jp/events/3872

さっそく@orangecloverさんがまとめてくれています。ありがとうございます!

http://d.hatena.ne.jp/orangeclover/20130518/1368845593

当日の資料はGitHubにおいてあります

モックの説明以降はずいぶん駆け足になって申し訳なかったのですが、それを見越してドキュメントと学習テストを充実させておいたので、それで勘弁して下さいw

ドキュメントは先程のGitHubのdocsディレクトリに、学習テストはsrc/test/groovyディレクトリにおいてあります。参加しなかった人でもなんとなく眺めればSpockがわかったような気になれる教材に仕上がっています。

また、当日@uehajから質問されてた「oldはどのように値を保持しているのか」という質問に対してコピーして持ってるのではないかと適当なことを言ってしまいましたが、裏で@kiy0takaさんが調べてくれていました。

ということでAST変換で先に評価して値を保持しているそうです。

ではでは、新運営委員長の@y_ugrails連携説明している暇ないからLTよろしくと無茶ぶりした@gantawitter、参加者のみなさんおつかれさまでした!

Struts1ユーザにGrailsをオススメする6の理由 - The search is over -

Struts1のEOLがアナウンスされました。 最後のリリースから長く時間が経過しており、実質開発は終了している状態でしたが、このタイミングでのアナウンスとなりました。

アナウンスの中では、次の乗り換え先として、Struts2・Spring Web MVCGrails・Stripesといったフレームワークがお薦めされていますが、私はダンチな生産性を提供するGrailsをお勧めします。

私のフレームワーク遍歴は、Struts1、WicketSeasar2、Springと色々渡り歩いてきましたが、ここ1年はがっつり業務でGrailsを使用しています。 1年がっつり使ってみた経験から、Struts1ユーザにGrailsをお勧めする理由をいくつか考えてみました。

Grailsは既存Javaフレームワークの延長線上にある

Grailsを支える基盤は、Spring・Hibernateといった、Javaの世界でよく知られ、また広く使われているフレームワークです。 ビューはSpring MVCをベースとしており、モデルはHibernateをベースとしています。 このため、既存のJavaシステムで、これらフレームワークの知識を有している場合は、そのノウハウを思う存分活用できます。

Struts1ユーザからみてGrailsをお勧めする理由の1つは、Grailsのコントローラがリクエスト駆動ベースであることです。 WicketJSFなどのように、コンポーネントベースではありません。 このため、Struts1でActionクラスを実装してしてきたユーザにとって、GrailsのControllerは非常に馴染みやすいはずです。

さらに、Java EE互換である点も重要です。 Grailsでは、単にgrails warとコマンドを実行するだけでwarファイルを生成できます。 このwarファイルは、Tomcatなど今までStrutsアプリケーションをデプロイしてきたアプリケーションサーバにデプロイ可能です。 既存のアプリケーションサーバが使用できるため、今までの運用ノウハウも引き続き活用できます。

GroovyはJava開発者のための言語である

RubyPythonといった言語に比べて、Javaコードの冗長さは、よく批判の対象になります。 Groovyの目標は、常に、このJavaの定型的な冗長さを排除し、Java開発者に異次元の生産性を提供することです。

GroovyはJava言語の延長にある、Java開発者のための言語です。 Java開発者のための言語である証拠に、一部の例外を除き、Javaのコードは、Groovyのコードとして実行可能です。 これは、Groovyのシンタックスは、Javaのシンタックスと互換があるということです。 そのため、Javaプログラマであれば、今すぐにでもGroovyのコードが書き始められます! 世界中の多くのJavaエンジニアは、潜在的なGroovyエンジニアといっても過言ではないでしょう。 若干言い過ぎたかも知れませんが、Javaエンジニアにとって非常に学びやすい言語であることは間違いありません。

もちろん、Javaコードとの親和性だけでなく、GroovyにはJava開発者に異次元の生産性を提供する多くの機能が含まれています。 型宣言の省略、クロージャ、便利なコレクション操作、メタプログラミング、演算子のオーバーロードなど、Rubyや他言語で羨ましかった機能、またはそれ以上の機能がGroovyで使用できます。

Grailsは、このGroovyでコードを書きます。 Struts1ユーザにとっては、このGroovyが逆に障壁になるかと思います。 いくらJavaとの親和性が高いとはいえ、言語が変わるというのは大きな話です。 しかし、新たなフォースを手に入れるには常に痛みが付き物です。 Java開発者にとって、比較的少ない痛みで、異次元の生産性が手に入るのはGroovy以外にありません。

既存のJava資産をそのまま活用できる

GroovyとJavaの親和性の高さも、既存のJava開発者にとって大きな魅力の1つです。 Groovyからは、既存のJavaのライブラリ・フレームワークといった、Javaのコードを簡単に呼び出すことができます。 これは、Javaのコード内でJavaのコードを呼ぶように、Groovyのコード内でJavaコードを呼び出せます。

また、GrailsでもJavaライブラリや、Javaのコードと連携する仕組みが予め用意されています。 jar形式のJavaライブラリは、単にlibディレクトリにファイルを置くだけで、簡単にアプリケーションの依存関係として追加できます。 Javaコードには、デフォルトでsrc/javaというディレクトリがGrailsによって用意されており、このディレクトリでJavaコードをすぐに書き始めることができます。

もし、Strutsアプリケーションで使用してた、ライブラリや、ビジネスロジックなどの資産がある場合は、 Grails上でもその資産を活用できます。

フレームワークの連携で悩むことはありません

Struts1を単独で使用していたユーザもいるかも知れませんが、SpringやHibernateといった、他のフレームワークと組み合わせて使用していたユーザも多くいると思います。 これらフレームワークを組み合わせて使用する場合は、自分で連携の設定をしなければなりません。 この連携の設定を調べるために、インターネットを彷徨い、気がつくと1日2日経過していた、という話は珍しくありません。 一度連携できたら、使い回すだけでしょ?と思いがちですが、フレームワークのバージョンに伴い設定方法が変更になり、また1日2日インターネットにダイブする羽目になります。

たかが数日と思うかもしれませんが、迅速な開発が求められる昨今においては、これは非常に足かせになります。ぐぐってる暇なんてありません。

Grailsフルスタックフレームワークです。create-appとコマンドを打てば、数秒で全てのフレームワーク連携が完了した雛形が手に入ります。 Struts1のように、フレームワークの連携で悩むことは、Grailsではありません。

進化し続けるフレームワーク

Struts1ユーザにとって、乗り換えたフレームワークが、数年で使い物にならなくなる自体はあまり嬉しくないでしょう。 そのため、現在Grailsの開発が活発に行われているか、今後もメンテンナスが続くかは関心の1つかと思います。

現在Grailsは、VMwareとEMCとの合併会社Pivotalの配下にある、SpringSourceで開発が行われています。 開発自体はオープンに行われており、ベンダー依存を気にする必要は今のところ無いかと思います。 ソースコードGithubで公開されています。

開発は活発に行われており、世の中の動向に合わせて新しい機能が今なお追加されています。 ただ、新しい機能がどんどん追加されているため、正直枯れているとは言えない状況です。

Sturts1ユーザにとっては、安定性が心配になるかと思いますが、商用レベルでの使用に十分耐えるレベルではあるので、安心してください。

学習環境

Grailsドキュメントは非常にしっかりと書かれており、日本語版も鋭意翻訳中です。

まとめ

GrailsJava開発者のための、フレームワークです。 Java開発者が魅力を感じないとしたら、ほんと訴求先がないと言っていいかもしれません(言語、フレームワークは素晴らしいのは間違いないのですが、Java開発者以外には訴求力が弱い)。

ということでSturts1ユーザの皆さん、一緒にGrailsやりましょう!

GrailsでCucumberを使用する

RubyRailsの世界では、プレーンテキストで記述できる受け入れテストツールとして有名なCucumberですが、Pure-Javaで実装されたcucumber-jvmのお陰で、Grailsでもプラグインをインストールすることで簡単にcucumberが利用できるようなります。

準備

cucumber pluginをBuildConfig.groovyに追加しインストールします。

plugins {
    ...
    test ":cucumber:0.8.0"
}

featureの準備

デフォルトではcucumberに必要なfeatureやstepファイルはtest/functionalに格納します。これは設定ファイルで変更することも可能です。 まず始めに以下の様なfeatureをtest/functional/NewBook.featureに作成します。

#language: ja

フィーチャ: 新しい本の登録
    本の所有者として
    私はBookTrackerに自分の本を追加したい
    自分でそれを覚えておく必要がないように

シナリオ: 新しい本
    前提 BookTrackerを開く
    もし "Specification by Example"を追加する
    ならば "Specification by Example"の詳細を参照できる

日本語でfeatureファイルを記述するには#language: jaをファイルの先頭に追加する必要があります。

実行

実行はGrailsのfuctionalテストとして実行します。

grails test-app functional:
grails test-app :cucumber

まだstepを実装していないため、以下のようなエラーが出力されるはずです。

You can implement missing steps with the snippets below:

前提(~'^BookTrackerを開く$') { ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
もし(~'^"([^"]*)"を追加する$') { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
ならば(~'^"([^"]*)"の詳細を参照できる$') { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}

stepの実装

test/functional/steps/Book_steps.groovyを作成してstepを実装していきます。デフォルトではstepはtest/functional/steps/ディレクトリに格納する必要があります。step実装の主な流れは以下のようになります。

  • 先ほどの出力をコピーする
  • PendingExceptionをインポートする
  • JAのlanguageをインポートする
  • (ダブルクオートで使用している場合は$をエスケープする、GStringの制約のため)

これを実施すると以下のようなファイルになります。

import cucumber.runtime.PendingException

this.metaClass.mixin(cucumber.api.groovy.JA)
// 以下のようにstaticインポートしても同じ
//import static cucumber.api.groovy.JA.*

前提(~'^BookTrackerを開く$') {->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
もし(~'^"([^"]*)"を追加する$') { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
ならば(~'^"([^"]*)"の詳細を参照できる$') { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}

ここで再度テストを実行すると、PendingExceptionがスローされます。それでは次に必要な実装を追加していきます。

前提(~'^BookTrackerを開く$')

必要なセットアップがあるならここで実装を追加しますが、今回は特に必要なセットアップが存在しないため、単に何もしないように変更します。

前提(~'^BookTrackerを開く$') {->
    // NOP
}

もし(~'^"([^"]*)"を追加する$')

まず、必要となるdomainとcontrollerを実装します。今回作成するcontrollerは単にJSONを返すインタフェースになっています。

domain:

package books

class Book {
    String title
}

controller:

package books

import grails.converters.JSON

class BookController {

    def add() {
        render new Book(params).save() as JSON
    }

}

次にstepを次のように実装します。

もし(~'^"([^"]*)"を追加する$') { String bookTitle ->
    bookController = new BookController()
    bookController.params.title = bookTitle
    bookController.add()
}

ここでは詳細には説明しませんが、これはGrailsにおけるcontrollerのテストの仕組みにそってテスト実装する必要があります。 この状態で再度テストを実行するとjava.lang.IllegalStateExceptionがスローされます。 これを解消するには、コントローラが外部から呼ばれているように見せるために、コントローラのテストに必要なセットアップとクリーンアップのコードを追加する必要があります。

Before & After

test/functional/hooks/env.groovyに以下のようなファイルを追加します。これによりコントローラをテストする際のモック機能が有効になります。

import org.codehaus.groovy.grails.test.support.GrailsTestRequestEnvironmentInterceptor

this.metaClass.mixin(cucumber.api.groovy.Hooks)

GrailsTestRequestEnvironmentInterceptor scenarioInterceptor

Before() {
    scenarioInterceptor = new GrailsTestRequestEnvironmentInterceptor(appCtx)
    scenarioInterceptor.init()
}

After() {
    scenarioInterceptor.destroy()
}

この状態でテストを実施すると、先ほどのjava.lang.IllegalStateExceptionがスローされずにstepの最後のブロックで、PendingExceptionが発生します。

ならば(~'^"([^"]*)"の詳細を参照できる$')

以下のように実装します。

ならば(~'^"([^"]*)"の詳細を参照できる$') { String bookTitle ->
    def actual = bookController.response.json

    assert actual.id
    assert actual.title  == bookTitle
}

controllerのレスポンスに格納されているjsonの値を参照し、saveした際に生成されるidと、paramsから取得したtitleが正しく設定されているか検証しています。

最後にもう一度テストを実行してみます。

$ grails test-app functional:
| Server running. Browse to http://localhost:8080/cucumber
| Running 1 cucumber test...
| Completed 1 cucumber test, 0 failed in 2429ms
| Tests PASSED - view reports in /Users/yamkazu/IdeaProjects/grails-examples/cucumber/target/test-reports

うまくテストが通りました。

まとめ

Grailsで簡単にCucumberが利用できました! 次回は?Gebとの連携を紹介します。

参考

GradleのプロジェクトでGroovy Consoleを起動する

GradleのプロジェクトでGroovy Consoleを起動できると、gradleプロジェクトで依存するライブラリとプロダクトコードをクラスパスにロードした状態で、ちょっとしたコードの動作を試せるようになる。

build.gradleに以下を追加してgradle consoleを実行する。

task console(dependsOn: 'classes', type: JavaExec) {
    main = 'groovy.ui.Console'
    classpath = sourceSets.test.runtimeClasspath
}

classpathsourceSets.main.runtimeClasspathでも問題ないが、testにしておくとtestスコープにspockなどを含んでおき、spockを使用して動作確認をしたい場合に都合が良い。

参考: http://piraguaconsulting.blogspot.jp/2012/02/gradle-groovy-console.html