ラベル Grails の投稿を表示しています。 すべての投稿を表示
ラベル Grails の投稿を表示しています。 すべての投稿を表示

2012年4月1日日曜日

会社員、辞めました。

一応、今日は真面目モードで。

みけです。

TopGate、辞めました。


以前にもお伝えしたとおり、

昨日、3月31日を以って、

株式会社TopGateを退職いたしました。


社長の加藤昌樹氏、

Google API Expertの小川信一氏や、

Android全般に詳しいわかめまさひろ氏、

世界の揺下こと山下武志氏、

をはじめとする優秀な技術集団の一員として

いっしょに働けたことを誇りに思います。


会社員を辞める?


さて、これも私をTwitterでフォローしている方や、

ブログを読まれている方、

実際に私とお会いしたことのある方などには、

ご存知の方も多いと思いますが、

私は所謂普通の人と比べてみると、

ムラの多い人間であるようです。


実際、今冬の冬眠状態や、

今私が認識している睡眠相前進症候群状態を始めとして、

所謂普通の人と比べてメンタル面で

非常に奇異な特性を持っているようです。


そういった特性は認識しているのは

まあ、どうでもよくって???

問題は所謂普通の人との付き合い方にあります。


睡眠相がずれていくのは、

昼間、普通の人に対して資本が求める働きを

私は応えられないとか、

冬はほとんど成果が出せないなどという

問題をはらんでいます。


そのうえで、身体を普通の人に合わせるのか、

生活を身体に合わせるのかという問いを立てた時に、

私としては後者のほうを選択することにしました。


私自身としては、もし下記の条件でも雇っていただける会社があるなら、

お話だけは伺いたいと思っています。

  • 一日の勤務時間は6時間まで。延長は一切なし
  • 週の勤務日数は4日まで
  • 覚醒時間が常にずれていくので、自宅勤務可能
  • 夏季休暇はいらないですが、冬季休暇を3ヶ月いただける
  • Groovyをやらせていただける

おそらく、この条件を満たせるような会社はないと

思っていますので、

会社員を辞めるという決断をいたしました。


身体に合わせるという選択肢


私と同様に身体的な問題で成果を発揮できない状態で

いる人間というのは、

資本の側から見ると厄介な存在だと思う一方で、

その人間の側からしても資本の欲求に応えられないという

フラストレーションの溜まるものであります。


そのフラストレーションにより自身がなくなり、

またさらに成果を発揮できなくなって、

フラストレーションが溜まるという状態は、

おそらく私がこの30数年の間に何回も経験したものです。


いい加減、この課題に対して何らかのアクションを

取らないといけないわけで、

そのアクションとして、先程も記述しましたが、

生活を身体に合わせるという選択肢を採択することにしました。


個物と一般


もちろん、皆さんも自分の身体というのをご存知なわけで、

私のような選択をすることは、

「みんな頑張っているのに、なぜお前は…」

と言いたくなるところもあるでしょう。


私自身、このブログで幾度か表明していますが、

マルクスを信望しています。

マルクスの考え方というのは非常に徹底的なもので、

交換が成立しないなら価値はなかったという考え方は

非常に説得力があります。


マルクスの思想の背景には、

「個物はいかにして一般性を獲得するか」

という問題設定があるような気がします。


そうした個別唯物的な考え方に照らした時に、

「みんなが…」という社会主義的な考え方には

相容れないものがあります。


私は個物と一般という対立は無意味なものだと考えていて、

その両項の差異を明確にしていくという方法があるのではないかと思っています。


ちょっと最後の部分はうまく伝えられていない気がしますが…


欲望


経済的な行為であれ、なんであれ、

人間には欲望があって、欲望が行動のすべての源泉であると思います。


私も何かを動かすことや、何がどう動いていることを理解すること、

こういったことに非常に欲望を感じています。

(他にもありますけどね)


GroovyというJavaでできた言語が動いていることへの興味、

そして何かを動かせるという興味から

受け取る欲望の充足が非常に大きいということを私は感じています。


もっとこの欲望とその充足を満たしてみたいと思います。


そこで、以前表明したように

5月くらいには会社として登記できるように

会社を作ることにします。


計画


私はgradle_jaというコミュニティを立ち上げて、

gradleというGroovyのプロダクトの普及を当面やっていこうと思っています。

が、5月スタートアップに向けて4月はその準備、

そのあとは当面の資金稼ぎのためのサービス開発、

GrailsというGroovyのプロダクトの研究・調査、

Spring RooというJavaプロダクトの研究・調査、

という感じで一カ月を過ごしていこうと考えています。


以上、能書きが多かったと思いますが、

今後とも皆様よろしくお願いします。




2012年1月14日土曜日

Grails2.0のDomainをTDDしてみる。

出展はいつもどおりの『Grails徹底入門』
の96ページにあるモデル図から。

ここから、今回の対象部分のモデルを抜き出したのが以下の図。
で、今回はこのうち、Shipmentの部分をTDDしていきます。


ドメインの作成


ドメインの作成はいたって簡単です。


$ grails create-domain-class Shipment


これだけで、Shipmentドメインクラスと、ShipmentTestsテストクラスが生成されます。

Shipment.groovy

package grailsshop

class Shipment {

    static constraints = {
    }
}


Shipment.groovy

package grailsshop
import grails.test.mixin.*
import org.junit.*

@TestFor(Shipment)
class ShipmentTests {

    void testSomething() {
        fail()
    }
}


なお、テストクラスはデフォルトではfailになるようになっています。

最初のテスト


Shipment(出荷)はイベントなので、必ず日付をもちます。したがって、nullは禁止です。

それをテストに書きます。

なお、Grails2.0はJUnit4対応しているので、アノテーションを用いることでテストメソッドであることを示せます。

ここではテストメソッド名も変更しています。

また、validateのテストになりますので、前回の結論に書いておいたようにmockForConstraintsTestsメソッドを最初に用いておきます。

Shipment.groovy

package grailsshop
import grails.test.mixin.*
import org.junit.*

@TestFor(Shipment)
class ShipmentTests {

    @Test
    void validateDate() {
        mockForConstraintsTest(Shipment)
        def object = 'date'

        def shipment = new Shipment()
        assert shipment.validate() == false
        assert shipment.errors[object] == 'nullable'
    }
}


実行結果は次のようになります。


grails> test-app grailsshop.Shipment
| Running 1 unit test... 1 of 1
| Failure:  validateDate(grailsshop.ShipmentTests)
|  Assertion failed:

assert shipment.validate() == false
       |        |          |
       |        true       false
       grailsshop.Shipment : null

 at grailsshop.ShipmentTests.validateDate(ShipmentTests.groovy:20)
| Completed 1 unit test, 1 failed in 1930ms
| Packaging Grails application.....
| Tests FAILED  - view reports in target/test-reports
grails>


まあ、Shipmentにはdateというフィールドをまだ実装していないので、落ちるのもやむなしです。

暗黙のnullable : false


そこで、実装に行きます。

とりあえず、ShipmentクラスにDate型のdateを持たせてみます。

Shipment.groovy

class Shipment {

    /**
     * 出荷日付.
     */
    Date date

    static constraints = {
    }
}


この状態でテストを実行してみます。


grails> test-app grailsshop.Shipment
| Completed 1 unit test, 0 failed in 152ms
| Tests PASSED - view reports in target/test-reports
grails>


というわけで、とくにnullチェックの実装を入れていませんが、テストが通りました。

Grailsのドメインクラスにあるフィールドはデフォルトでnullablefalseのようです。

適当に制約をいれていく


Shipment(出荷)、捉え方によるけど、未来の日付は入れられないことにしておきましょう。

(出荷予定であれば話は別ですが…)

それをテストに書きます。

ShipmentTests.groovy

@TestFor(Shipment)
class ShipmentTests {

    @Test
    void validateDate() {
        mockForConstraintsTest(Shipment)
        def object = 'date'

        def shipment = new Shipment()
        assert shipment.validate() == false
        assert shipment.errors[object] == 'nullable'

        shipment = new Shipment(date: dayFromToday(1))
        assert shipment.validate() == false
        assert shipment.errors[object] == 'max'
    }
}


dayFromToday(int)は今日からの日付を取るユーティリティーメソッドです。0を指定すると今日の日付、1を指定すると明日の日付が取得できます。

翌日の日付であった場合は、エラーとなるというテストを記述しています。

で、テストの結果は次のとおりになります。


grails> test-app grailsshop.Shipment
| Running 2 unit tests... 1 of 2
| Failure:  validateDate(grailsshop.ShipmentTests)
|  Assertion failed:

assert shipment.validate() == false
       |        |          |
       |        true       false
       grailsshop.Shipment : null

 at grailsshop.ShipmentTests.validateDate(ShipmentTests.groovy:24)
| Completed 2 unit tests, 1 failed in 96ms
| Packaging Grails application.....
| Tests FAILED  - view reports in target/test-reports
grails>


まず、validate()Assertが落ちます。

落ちた原因を探りたいので、一度テストを修正します。

ShipmentTests.groovy

@TestFor(Shipment)
class ShipmentTests {

    @Test
    void validateDate() {
        mockForConstraintsTest(Shipment)
        def object = 'date'

        def shipment = new Shipment()
        assert shipment.validate() == false
        assert shipment.errors[object] == 'nullable'

        shipment = new Shipment(date: dayFromToday(1))
        shipment.validate()
        assert shipment.errors[object] == 'max'
    }
}


テスト結果


grails> test-app grailsshop.Shipment
| Running 2 unit tests... 1 of 2
| Failure:  validateDate(grailsshop.ShipmentTests)
|  Assertion failed:

assert shipment.errors[object] == 'max'
       |        |     ||       |
       |        |     |date    false
       |        |     null
       |        org.codehaus.groovy.grails.plugins.testing.GrailsMockErrors: 0 errors
       grailsshop.Shipment : null

    at grailsshop.ShipmentTests.validateDate(ShipmentTests.groovy:25)
| Completed 2 unit tests, 1 failed in 81ms
| Packaging Grails application.....
| Tests FAILED  - view reports in target/test-reports
grails>


内容からわかるようにエラーがないということです。

ここで、正しくvalidate()でエラーとなるようにドメインクラスを修正します。

Shipment.groovy

class Shipment {

    /**
     * 出荷日付.
     */
    Date date

    static constraints = {
        date(max: new Date())
    }
}



制約を加えたらテストを実行します。


grails> test-app grailsshop.Shipment
| Completed 2 unit tests, 0 failed in 124ms
| Tests PASSED - view reports in target/test-reports
grails>


ちゃんとパスします。

念のため、今日も大丈夫か確認します。

ShipmentTests.groovy

@TestFor(Shipment)
class ShipmentTests {

    @Test
    void validateDate() {
        mockForConstraintsTest(Shipment)
        def object = 'date'

        def shipment = new Shipment()
        assert shipment.validate() == false
        assert shipment.errors[object] == 'nullable'

        shipment = new Shipment(date: dayFromToday(1))
        assert shipment.validate()
        assert shipment.errors[object] == 'max'

        shipment = new Shipment(date: dayFromToday(0))
        shipment.validate()
        assert shipment.errors[object] == null
    }
}


ポイントとしては、validate()が通るケースでは、assert shipment.validate() == trueのテストを行わない方が良いです。

理由は、これから他にもvalidateするものが増えるので、後々にテストが通らなくなるからです。

テストの実行結果は次のようになります。


grails> test-app grailsshop.Shipment
| Completed 2 unit tests, 0 failed in 81ms
| Tests PASSED - view reports in target/test-reports
grails>


リレーションに関するフィールドの追加とテスト


次にリレーションに関するフィールドの追加とテストです。

先に掲載したモデルから、ShipmentWarehouseOrderの関係は、次のようになります。

  • Shipment - Warehouse
    • ShipmentからみてWarehouseは唯一つ存在し、かつその参照先を保持する必要がある。
    • WarehouseからみてShipmentは0または1つ存在し、その参照先は保持しなくて良い。
  • Shipment - Order
    • ShipmentからみてOrderは唯一つ存在し、かつその参照先を保持する必要がある。
    • OrderからみてShipmentは0または1つ存在し、その参照先は保持しなくて良い。

こういうのを一般的には一対一の片方向の関連とかなんとかいうらしいです。

Grailsのドメインにおいて、これを実現するのがbelongsToです。

では、おもむろにテストを書きます。

ここでは、暗黙のnullable : falseを利用します。

まずはOrderから…

ShipmentTests.groovy

@TestFor(Shipment)
class ShipmentTests {

    @Test
    void validateOrder() {
        mockForConstraintsTests(Shipment)
        def object = 'order'

        def shipment = new Shipment()
        assert shipment.validate() == false
        assert shipment.errors[object] == 'nullable'
    }
}


テストを実行します。


grails> test-app grailsshop.Shipment
| Running 3 unit tests... 1 of 3
| Failure:  validateDate(grailsshop.ShipmentTests)
|  java.lang.NoSuchMethodError: grailsshop.Shipment.getBelongsTo()Ljava/lang/Object;
 at org.grails.datastore.mapping.reflect.ClassPropertyFetcher$GetterPropertyFetcher.get(ClassPropertyFetcher.java:326)
 at org.grails.datastore.mapping.reflect.ClassPropertyFetcher.getPropertyValueWithFetcher(ClassPropertyFetcher.java:218)
 at org.grails.datastore.mapping.reflect.ClassPropertyFetcher.getStaticPropertyValue(ClassPropertyFetcher.java:233)
 at org.grails.datastore.mapping.model.config.GormMappingConfigurationStrategy.establishRelationshipOwners(GormMappingConfigurationStrategy.java:271)
 at org.grails.datastore.mapping.model.config.GormMappingConfigurationStrategy.getOwningEntities(GormMappingConfigurationStrategy.java:716)
 at org.grails.datastore.mapping.model.AbstractPersistentEntity.initialize(AbstractPersistentEntity.java:79)
 at org.grails.datastore.mapping.model.AbstractMappingContext.addPersistentEntityInternal(AbstractMappingContext.java:150)
 at org.grails.datastore.mapping.model.AbstractMappingContext.addPersistentEntity(AbstractMappingContext.java:135)
 at grails.test.mixin.domain.DomainClassUnitTestMixin.mockDomain(DomainClassUnitTestMixin.groovy:124)
 at grails.test.mixin.domain.DomainClassUnitTestMixin.mockDomain(DomainClassUnitTestMixin.groovy:120)
| Failure:  validateDate(grailsshop.ShipmentTests)
|  java.lang.NullPointerException
 at org.grails.datastore.mapping.core.DatastoreUtils.unbindSession(DatastoreUtils.java:362)
 at grails.test.mixin.domain.DomainClassUnitTestMixin.shutdownDatastoreImplementation(DomainClassUnitTestMixin.groovy:109)
| Running 3 unit tests... 2 of 3
| Failure:  validateOrder(grailsshop.ShipmentTests)
|  Assertion failed:

assert shipment.errors[object] == 'nullable'
       |        |     ||       |
       |        |     |order   false
       |        |     null
       |        org.codehaus.groovy.grails.plugins.testing.GrailsMockErrors: 1 errors
       |        Field error in object 'grailsshop.Shipment' on field 'date': rejected value [null]; codes [grailsshop.Shipment.date.nullable.error.grailsshop.Shipment.date,grailsshop.Shipment.date.nullable.error.date,grailsshop.Shipment.date.nullable.error.java.util.Date,grailsshop.Shipment.date.nullable.error,shipment.date.nullable.error.grailsshop.Shipment.date,shipment.date.nullable.error.date,shipment.date.nullable.error.java.util.Date,shipment.date.nullable.error,grailsshop.Shipment.date.nullable.grailsshop.Shipment.date,grailsshop.Shipment.date.nullable.date,grailsshop.Shipment.date.nullable.java.util.Date,grailsshop.Shipment.date.nullable,shipment.date.nullable.grailsshop.Shipment.date,shipment.date.nullable.date,shipment.date.nullable.java.util.Date,shipment.date.nullable,nullable.grailsshop.Shipment.date,nullable.date,nullable.java.util.Date,nullable]; arguments [date,class grailsshop.Shipment]; default message [Property [{0}] of class [{1}] cannot be null]
       grailsshop.Shipment : null

 at grailsshop.ShipmentTests.validateOrder(ShipmentTests.groovy:39)
| Completed 3 unit tests, 3 failed in 75ms
| Packaging Grails application.....
| Compiling 1 source files.
grails>


なんか、関係のないvalidateDateまで落ちてしまいました(´・ω・`)

このあたりはGrailsの改善に期待するしかなさそうです…

テストが通るように実装をします。

Shipment.groovy

class Shipment {

    /**
     * 出荷日付.
     */
    Date date

    static belongsTo = [
            /**
             * 発注.
             */
            order : Order
    ]

    static constraints = {
        date(max: new Date())
    }
}



実装したら、テストを実行します。


grails> test-app grailsshop.Shipment
| Completed 3 unit tests, 0 failed in 168ms
| Tests PASSED - view reports in target/test-reports
grails>


今回はすんなり通りました。何だったんでしょう?あの落ちっぷりは…



さて、Warehouseの方も同様にテスト、実装します。

ShipmentTests.groovy

@TestFor(Shipment)
class ShipmentTests {

    @Test
    void validateWarehouse() {
        mockForConstraintsTests(Shipment)
        def object = 'warehouse'

        def shipment = new Shipment()
        assert shipment.validate() == false
        assert shipment.errors[object] == 'nullable'
    }
}


Shipment.groovy

class Shipment {

    /**
     * 出荷日付.
     */
    Date date

    static belongsTo = [
            /**
             * 発注.
             */
            order : Order,

            /**
             * 倉庫.
             */
            warehouse : Warehouse
    ]

    static constraints = {
        date(max: new Date())
    }
}


結論


うむ、ほんとうは@Mockをやりたかったのだが、書いている量が半端なくなってきたので、次回に…




2012年1月7日土曜日

Grails2.0のDomainのUnit Testハマリどころ

今日はGrailsのDomainテストのハマリどころについてのメモです。
と言っても、ひとつしか書いていませんが…

ConstraintsのValidationテスト


前回の記事ではValidationのテストについて少し取り扱っています。

Book.groovy

class Book {

    String name

    String author

    int price

    static constraints = {
        name (blank: false)
        author (blank: false)
        price (min: 0)
    }
}


BookTests.groovy

@Test
    void validation() {
        def book = new Book(name: '吾輩は猫である', price: -1)
        assert book.validate() == false
    }


このテストで分かるのはこのままでは登録できないということです。

ただ、残念なことに何が悪くてValidationで引っかかったのかがわかりません。

この際に役立つのがPOGOに自動的に付与されるerrorsというメンバーです。

くせもののerrors


このerrorsにはvalidate()でチェックされたエラーのスナップショットが格納されています。

上記のコードの例では、Bookクラスには著者の情報が未設定であり、不適切な価格情報が設定されているという状態です。

したがって、errorsにはauthorpriceというメンバーが存在します、いや、するはずです。

では、本当にpriceで引っかかっているのか確認するテストを書きます。


@Test
    void validation() {
        def book = new Book(name: '吾輩は猫である', price: -1)
        assert book.validate() == false
        assert book.errors['price'] == 'min'
    }


このテストはvalidate()でチェックされたエラーのうち、priceに対するエラーはminであることをテストします。

おもむろに実行します。

実行結果(一部略)


| Failure:  validateQuantity(grailsshop.Book)
|  Assertion failed:

assert book.errors['price'] == 'min'
       |   |      |        |
       |   |      |        false
       |   |      Field error in object 'grailsshop.Book' on field 'price': rejected value [0];
       |   Field error in object 'grailsshop.Book' on field 'price': rejected value [0];
       grailsshop.Book: null


実際はもっと長ったらしいエラーメッセージが出ますが、Field errorの部分の内容を見るかぎりではこのテストは通ってもよさそうなものなのですが、ダメなんですね。

書き方間違えたのかと思って、公式サイトを確認しましたが、合っているようです。

mockForConstraints


そんなこんなで、混乱していたところ、Grailsの第一人者であられる@tyamaさんからアドバイスをいただきました。


ふむ、mockForConstraints(Class<T>)を使えばよいとな。

というわけで、おもむろに入れてテストを通してみる。


@Test
    void validation() {
        mockForConstraintsTests(Book)
        def book = new Book(name: '吾輩は猫である', price: -1)
        assert book.validate() == false
        assert book.errors['price'] == 'min'
    }


そして、テスト実行。


grails> test-app grailsshop.Book
| Completed 1 unit tests, 0 failed in 361ms
| Tests PASSED - view reports in target/test-reports


おお、通った。

結論


とりあえず、Grails2.0constraintsValidationテストをする場合は、黙ってmockForConstraintsTestsを使っとけという話ですな。


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をダウンロードしてパスを通してあげて下さい。