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というのは、Spring、Hibernate、Groovyを元にして作られた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に対するテストを書いていきます。
name
がnull
だったらエラーとなるか?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 件のコメント:
コメントを投稿