2012年1月3日火曜日

はじめてGrailsをさわるオッサンがGrails2.0でDomainを作ってみた


An elder engineer, new for Grails, got involved into a trouble on unit testing of Grails2.0 domain classes.

Grails2.0



昨年末にGrails2.0がリリースされました。
というわけで、正月のお休み期間を利用して触ってみることにしました。

とはいえ、実は初めてGrailsを触るので、『Grails徹底入門』をお手本に写経することにしました。








Grailsというのは、SpringHibernateGroovyを元にして作られたJVM上で動作するWebアプリケーションフレームワークです。
設定よりも規約に準拠することにより高い生産性を発揮することを目標に作られています。
また、GroovyとJavaの親和性は高く、既存のJava資産を有効に活用できるフレームワークでもあります。


Domain


あらまし


アプリケーションを作成する場合、まず問題がどういった要素が関係しているか分析を行います。
これらの要素と関連はWebアプリケーションとか、Androidアプリケーションとかそういったプラットフォームに依存することなく存在します。
こういった要素と関連のことをModelと言ったり、Domainと言ったりします。
GrailsではDomainという言葉で指しているようです。
(テキトーなオッサンなのでツッコミ( `・∀・´)ノヨロシク)

BookとPublisher


『Grails徹底入門』では本屋さんを模してアプリケーションを作成していますので、本(Book)とか、出版社(Publisher)とかそういった類のDomainを定義しています。
というわけで、内容をパクって参考にして、BookとPublisherの関係を書いてみました。


関係性


まあ、難しい関係ではありませんね。
Bookからみて、Publisherは…

  • 一つだけある。
  • 必ず存在する。

という関係があります。

逆にPublisherからみて、Bookは…

  • 複数ある。
  • 存在しないこともある。

という関係があります。

まあ、こういう話は、ER図の本とか、データベース設計の本などの方が詳しいので、そちらに譲りましょう。
というわけで、このドメインをGrailsで作っていきたいと思います。

ドメインクラスの作成


create-app


まず、プロジェクトを作成します。


$ cd workspace
$ grails create-app BookShop


これで、workspaceディレクトリの下に次のような構造をもつBookShopプロジェクトディレクトリが作成されます。


なお、プロジェクト名が違っていますが、そこは、ほら、その、なんというか、大人な感じに、つまりいい感じに読んでいって下さい。


create-domain-class


次に、ドメインクラスを作成します。


$ cd BookShop
$ grails create-domain-class Publisher
$ grails create-domain-class Book


すると、次のようにDomainクラスとTestクラスが作成されます。

  • grails-app
    • domain
      • bookshop
        • Book.groovy
        • Publisher.groovy
  • test
    • unit
      • bookshop
        • BookTests.groovy
        • PublisherTests.groovy


これらの自動生成されたクラスを編集してドメインを実装していきます。

あと、Orderとかいうクラスがすでに追加されていますが、そこも、ほら、あの、なんていうか、その、大人な感じで( `・∀・´)ノヨロシク。

ドメインクラスの実装


メンバーを与える


先ほど書いた図を元にドメインクラスにメンバーを与えていきます。

Publisher.groovy

class Publisher {

    String name

}


Book.groovy

class Book {

    String name

    String author

    int price

    Date releaseDate

    String isbn13

    String imageUrl

    String description
}


はい、ここまでは難しくありません。

関係に関するメンバーを追加する


で、これに先ほどの関係のメンバーを付与します。

まず、Publisherの方ですが、Bookを複数持ちますので、hasManyを用います。

Publisher.groovy

class Publisher {

    String name

    static hasMany = [books: Book]
}


これによって、PublisherクラスにはbooksというList<Book>クラスのメンバーを持つことになります。

同様にBookクラスにもPublisherへの関連性をもたせます。
BookクラスはPublisherクラスを唯一つ持ちますので、belongsToを用います。

Book.groovy

class Book {

    String name

    String author

    int price

    Date releaseDate

    String isbn13

    String imageUrl

    String description

    belongsTo = [publisher: Publisher]
}


これによって、BooksクラスにはpublisherというPublisherクラスのメンバーを持つことになります。

ちなみに、これはGroovyのうれしいところですが、

Groovy ではメンバーを記述するだけで、自動的に setter / getter を自動生成してくれる

という機能があります。


Plain Old Java Object : POJO みたいに、 Plain Old Groovy Object : POGOなんて呼ばれているとかなんとか…

制約 (Constraints) を追加する


さて、さきほど関係性だけを記述しましたが、これではまだ十分ではありません。

  • 0個存在するとか、1個は必ず存在するとかの制約 (Constraints) の記述がない
  • 本の名前がないとか、著者がないとか、価格がマイナスとかっておかしい
  • 出版社の名前がないのというのもおかしい

関係性に関する制約にとどまらず、基本的な事柄に関する制約条件が満たされていません。

というわけで、これらの制約を盛り込んで行きましょう。

Publisher

まずは Publisher の方からですが、こんな制約が必要でしょうか…

  • 会社名の空白は禁止
  • 会社名が他の会社とかぶってはいけない

まあ、実際には2の制約はないでしょうけど、まあここではそういうことにしておきましょうwww

このような制約を与える場合に使うのが、 constraints です。

では、 Publisher に制約を与えてみましょう。

Publisher.groovy

class Publisher {

    String name

    static hasMany = [books: Book]

    static constraints = {
        name(blank: false, unique: true)
    }
}


追加した部分はこんな感じです。

  • 会社名 (name) に対しては、空白 (blank) は、禁止 (false) 。
  • 会社名 (name) は、唯一 (unique) に決まること (true) 。

これだけでいいんですか?これだけでいいんです( ー`дー´)キリッ

制約に関するテストを書く


疑心暗鬼に陥っているときは、テストを書いて安心するのがよいらしいです。

PublisherTests.groovy

@TestFor(Publisher)
class PublisherTests {

    @Test
    void validateName() {
        def publisher = new Publisher(name: 'hoge')
        assert publisher.validate()
    }
}


とりあえず、名前のある会社は大丈夫だよねっていうテストを書いてみました。
ちなみに、コンストラクターのところにある記号、name: 'hoge'というのは、Groovy特有の書き方で、Javaで書くとこんな感じになります。


@TestFor(Publisher)
class PublisherTests {

    @Test
    void validateName() {
        def publisher = new Publisher()
        publisher.setName('hoge')
        assert publisher.validate()
    }
}


では、実行しましょう。

Grailsのテストサポート


Grails interactive


さて、テストを実行したいところですが、コマンドgrails test-app bookshop.Publisherと実行するだけです。

実行するだけなのですが、色々とコマンドを覚えるのが面倒ですね。

そこで、grailsのコンソールを起動して、そちらに任せてしまいましょう。

ちなみに、grailsのコンソールとテキトーに書いていますが、正確にはgrails interactiveというらしいです

grailsのコンソールは何がいいかというと、もしコマンドがわからなければ、Tabを押すだけで、何を入力するべきか表示してくれます。

起動


起動方法は簡単です。


$ grails


と入力するだけです。

こんな感じでgrailsのコンソールが立ち上がります。


$ grails
| Enter a script name to run. Use TAB for completion:
grails>


何をするんですか?


何をすればいいかわかりませんね。Tabを押して下さい。


いろいろとコマンドが表示されます。

自分のやりたいコマンドを選ぶだけで構いません。

そうだテストをしよう


今はテストをやりたいので、test-appコマンドを入力します。

でも、このコマンドはすべてのテストを実行するコマンドなので、若干不便です。

たった一つのクラスを変更しただけなのに、すべてのテストを実行するのは後々時間がかかって大変です。

そういうのはJenkins先生とかにお願いしましょう。

そこで、test-appまで入力して、Tabを押して下さい。


どういうテストができるのか一覧が表示されます。

やりたいのはPublisherのテストなので、それっぽいやつを途中まで入力して、


Tabを押します。


お、なんかいい感じに入力できるコマンドが指定されてきました。

Publisherも途中まで入力して、


Tabを押します。


勝手に補完してくれますね。これで、Publisherだけのテストを実行できます。

では、おもむろに実行!


成功ですね。

え〜、ここでも、テストの個数が若干異なっていますが、そこは大人の事情ということで…

ちゃんとPublisherのテストを書く


blankに対するテストを書く


さて、今のテストはただ単に成功するだけの条件を書いたので、あまり意味のあるテストではありません。

そこでnameのvalidationに対するテストを書いていきます。

  • namenullだったらエラーとなるか?
  • nameが長さ0の文字列だったらエラーとなるか?

まず、この二つは確実に抑えておきたいところです。

これについてテストを書いていきます。

PublisherTests.groovy

@TestFor(Publisher)
class PublisherTests {

    @Test
    void validateName() {
        // name が null の場合
        def publisher = new Publisher()
        assert publisher.validate() == false

        // name が '' の場合
        publisher = new Publisher(name: '')
        assert publisher.validate() == false

        // name が長さ1以上の文字列の場合
        publisher = new Publisher(name: 'hoge')
        assert publisher.validate()
    }
}


テストを実行。


はい、成功です。

uniqueに対するテストを書く


さて、Publisherの制約条件にuniqueというのがありました。

読んで字の如くで、同じ名前の会社が登録されていたら、エラーとするというものなのですが、データベースが必要になってきます。

面倒くさそうになって来ました。

そこでGrailsでは、Unitテストにおいてデータベースがなくてもキャッシュだけでテストを行えるような仕組みを提供しています。

mockForConstraintsTests(Class<T>, List<T>)を用いてテストを実行します。

PublisherTests.groovy

@TestFor(Publisher)
class PublisherTests {

    @Test
    void validateName() {
        def existingPublisher = new Publisher(name: 'exists')
        mockForConstraintsTests(Publisher, [existingPublisher])

        // name が null の場合
        def publisher = new Publisher()
        assert publisher.validate() == false

        // name が '' の場合
        publisher = new Publisher(name: '')
        assert publisher.validate() == false

        // name がすでに存在する会社の名前と一致する場合
        publisher = new Publisher(name: 'exists')
        assert publisher.validate() == false

        // name が長さ1以上の文字列の場合
        publisher = new Publisher(name: 'hoge')
        assert publisher.validate()
    }
}


では、テストを実行してみましょう。


テスト通りました。

締め


以上、gdgdな感じの紹介になりましたが、こんな感じでDomainを作っていきます。

本当はもっとハマリどころがあるんですが、その前段まででめちゃくちゃ長くなってしまったので、ハマリどころについては次回やります。

ん、インストール方法が載っていない?

zipをダウンロードしてパスを通してあげて下さい。


0 件のコメント:

コメントを投稿