の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のドメインクラスにあるフィールドはデフォルトで
nullable
がfalse
のようです。適当に制約をいれていく
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>
リレーションに関するフィールドの追加とテスト
次にリレーションに関するフィールドの追加とテストです。
先に掲載したモデルから、
Shipment
とWarehouse
、Order
の関係は、次のようになります。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
をやりたかったのだが、書いている量が半端なくなってきたので、次回に…
0 件のコメント:
コメントを投稿