#参考
http://grails.jp/doc/latest/guide/testing.html
http://grails.org/doc/latest/guide/testing.html
はじめに
今回はGrailsのバージョンが2.4です。
標準でSpockでテストを書きます。
テストを実行するためには、以下のコマンドを実行します。
test-app
もしくは test-app unit:
コレで実行可能なユニットテストが実行されます。
他にもオプションなどありますが、基本的に気にしないほうがいいです。テストが実行されなかったりして、混乱のもとになります。
現在自分がGrailsのユニットテストの書き方を勉強中です。
この記事はまだ完成していません。
Qiitaの下書きが埋まってしまったので、とりあえずの公開となります。
今後、内容の修正、追記を都度行います。
#豆知識
ユニットテストなので、Grailsは環境をtestとして、DataSource.groovyとBootstrap.groovyをユニットテストが終わった後に読み込ます。
コレは、ユニットテストを環境(データベース)に依存させないためです。
integrationテストが続いて実行される場合には、その情報が利用されます。
#テスト用ファイルの生成方法
create-domain-class Test
とかcreate-controller Test
コマンド、create-tag-lib Test
を実行すると、それ用のユニットテスト用ファイルが自動で生成される。
デフォルトは以下のようなソースになっている。
package パッケージ名
import grails.test.mixin.TestFor
import spock.lang.Specification
/**
* See the API for {@link grails.test.mixin.domain.DomainClassUnitTestMixin} for usage instructions
*/
@TestFor(Test)
class TestSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "test something"() {
}
}
package パッケージ名
import grails.test.mixin.TestFor
import spock.lang.Specification
/**
* See the API for {@link grails.test.mixin.web.ControllerUnitTestMixin} for usage instructions
*/
@TestFor(TestController)
class TestControllerSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "test something"() {
}
}
コントローラで利用されるテストをしたい場合は、コントローラのテストクラスに@Mock(ドメイン名)
と行った形でモック宣言しておく
#制約のテスト
制約のテストに関しては、4種類のタイプがある。
1.普通のドメインクラス
2.普通のGroovyクラスだけど、@grails.validation.Validateable
が付いているクラス
3.コントローラ内に宣言されているコマンドオブジェクト
4.Config.groovyのgrails.validateable.classes
に指定されたクラス。
基本的にドメインクラスのテストは、コントローラやサービス内で利用される際に、意図したデータが取得されるかどうかの確認がメインになるはずなので、本来はコントローラ内のテストでテストされるはず。
##ドメインクラスのテスト
class Test {
String name
Integer age
static constraints = {
name()
age(min: 0)
}
static mapping = {
version(false)
}
}
package パッケージ名
import grails.test.mixin.TestFor
import spock.lang.Specification
/**
* See the API for {@link grails.test.mixin.domain.DomainClassUnitTestMixin} for usage instructions
*/
@TestFor(Test)
class TestSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "基本的なテスト方法"() {
when: 'ageが0未満'
def p = new Test(name:'koji', age:-1)
then: 'validationがコケるはず'
!p.validate()
when : 'ageが0以上'
p = new Test(name:'koji', age:0)
then:'validationが通るはず'
p.validate()
}
}
##コマンドオブジェクトのテスト
コントローラのテストとも言えるのかな?
class TestController {
def commandObjectHandling(TestCommand cmd) {
if(cmd.hasErrors()) {
render "Bad"
} else {
render "Good"
}
}
}
class TestCommand {
String name
Integer age
static constraints = {
name(blank:false, minSize: 1)
age(min: 1)
}
}
@TestFor(TestController)
class TestControllerSpec extends Specification {
void "Commandオブジェクトのテスト"() {
setup:
def co = new TestCommand(name:name, age:age)
// このテストの段階で、手動でvalidateしておくする必要がある。
// というのも、このテストの段階で手動でコマンドオブジェクト(TestCommand)をnewしてcontrollerに渡しているので、
// controller側ではコマンドオブジェクトのvalid()が実行されないため。
co.validate()
controller.commandObjectHandling(co)
expect:
response.text == result
where:
name |age || result
'koji'| 29 || 'Good'
'' | 29 || 'Bad'
}
void "Commandオブジェクトのテスト(バインディング)"() {
setup:
// こっちの場合、渡したプロパティをcontroller側で判断して自動的にコマンドオブジェクト(TestCommand)にバインディングしてくれて、
// そのタイミングでvalidate()も裏で実行してくれているから、手動でvalidate()を実行する必要はない。
params.name = name
params.age = age
controller.commandObjectHandling()
expect:
response.text == result
where:
name |age || result
'koji'| 29 || 'Good'
'' | 29 || 'Bad'
}
}
この例ではTestCommandコマンドオブジェクトを含むコントローラ用のテストクラスからテストしているけど、create-unit-test TestCommand
といった感じでTestCommandコマンドオブジェクトのテスト専用のユニットテストを用意するのもあり。
こんな感じ
package パッケージ名
import grails.test.mixin.TestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
import spock.lang.Specification
/**
* See the API for {@link grails.test.mixin.support.GrailsUnitTestMixin} for usage instructions
*/
@TestMixin(GrailsUnitTestMixin)
class TestCommandSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "Commandオブジェクトのテスト"() {
when:
def co = new TestCommand(name:'koji', age:29)
then:
co.validate()
!co.hasErrors()
co.errors.errorCount == 0
when:'エラーにする'
co.age = -1
then:
!co.validate()
co.hasErrors()
co.errors.errorCount == 1
co.errors['age'].code == 'min.notmet'
when:'さらにエラーを増やす'
co.name = ''
then:
!co.validate()
co.hasErrors()
co.errors.errorCount == 2
co.errors['age'].code == 'min.notmet'
co.errors['name'].code == 'blank'
when:'最後に正常系をもう一回'
co.name = 'koji'
co.age = 29
then:
co.validate()
!co.hasErrors()
}
}
##普通のGroovyクラスのテスト
実際のGrailsアプリと違って、パッケージ宣言が無いとだめっぽい?
src/groovy配下に普通のGroovyクラスを作成
package mysource
@grails.validation.Validateable
class MyValidateable {
String name
Integer age
static constraints = {
name(nullable: false, blank: false, minSize: 1)
age(range: 1..99)
}
}
テスト用ファイルの生成
create-unit-test MyValidateable
package パッケージ名
import grails.test.mixin.TestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
import mysource.MyValidateable
import spock.lang.Specification
/**
* See the API for {@link grails.test.mixin.support.GrailsUnitTestMixin} for usage instructions
*/
@TestMixin(GrailsUnitTestMixin)
class MyValidateableSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "test something"() {
when:
def obj = new MyValidateable(name:'koji', age:29)
then: 'no error!'
obj.validate()
!obj.hasErrors()
obj.errors.errorCount == 0
when:'エラーになるバージョン'
obj = new MyValidateable(name:'', age:29)
then: 'エラーの確認'
!obj.validate()
obj.hasErrors()
obj.errors.errorCount == 1
obj.errors['name'].code == 'blank'
when: 'エラー情報をクリアすると?'
obj.clearErrors()
then: '当然こうなる。'
!obj.hasErrors()
obj.errors.errorCount == 0
when: 'エラーにならないように修正すると?'
obj.name = 'koji'
then: '当然エラーにならない!'
obj.validate()
!obj.hasErrors()
obj.errors.errorCount == 0
}
}
Config.groovyで指定する場合
grails.validateable.classes = [com.demo.Book]
という感じで指定するらしい。
テストの書き方は同じみたい。(未確認)
#その他(GORMの簡単な動作テスト)
ドメインクラスで、hasManyなどで値を持っているもののテストをどうしてもしたい場合は、@MockアノテーションにそのドメインをしておすればOK.
@TestFor(Test)
@Mock([Test2])
class TestSpec extends Specification {
void "test"() {
when:
def p = new Test(name:'koji', age:29)
p.addToTest2s(new Test2(name:"a"))
p.addToTest2s(new Test2(name:"a"))
p.save()
def test2 = new Test2(name:"b").save()
def p2 = new Test(name:'tarou',age:50)
p2.addToTest2s(test2)
p2.save()
p.addToTest2s(test2)
p.save(flush:true)
then:
p.test2s.size() == 3
p2.test2s.size() == 1
Test2.count == 3
}
}
#所感
基本的に、ドメインでも普通のGroovyクラスでもコマンドオブジェクトでも、制約のテストはすべて基本的に同じ。(サンプルコードは別のことをやっているだけ)
ドメインの場合ダイナミックファインダーやらデータの保存やらがあるけど、それはGrails自体が用意している機能をそのまま利用するわけなので、ここの単体テストで自分でテストすることではないのかな。
あくまでコントローラやサービスで利用する際に、想定通りのデータが取得できる可動がのほうが大事だしテストしやすいと思う。