単体テストは、同じコードをコピペして数十もしくは、数百ケースといったテストを書く場合がある。
こんなとき、
「インターフェース変わったらどうしよう」
と心配になると思う。
単体テストでも、コードを少なくしてインターフェースに変更があっても軽微の修正ですませるようにしたい。
そんな時は、spockを使って単体テストでありがちな、コードの冗長化は where を使って無くすことにする。
javaからも使えるので、Grailsに限らず単体テストの冗長化に頭を悩ませている人は spock を使って欲しい。
単純な値の場合
「|」 で パラメタ化
1 2 3 4 5 6 7 8 |
where: address | name | age | result "test1@test.com" | "test1" | 20 | "OK" "test2@test.com" | "testA" | 20 | "名前が違う" "test3@test.com" | "test3" | 1000 | "年齢が異常" "test4@test.com" | "test4" | -20 | "年齢がありえない" |
のように、渡す値と結果を書いておく。
これを使って以下のように定義する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@Unroll def "checkAddressテスト #testfor"() { setup: // 事前にDBなどに値をいれたりする必要がある時はここに書く when: // テストの実行 def checkResult = someService.check(address, name, age) then: // 確認 checkResult.address == address checkResult.test1.id == test1 checkResult.age.id == age where: address | name | age | testfor "test1@test.com" | "test1" | 20 | "OK" "test2@test.com" | "testA" | 20 | "名前が違う" "test3@test.com" | "test3" | 1000 | "年齢が異常" "test4@test.com" | "test4" | -20 | "年齢がありえない" } |
このテストを実行すると、4個のテストが行われる。
where: で定義した1行単位に処理が行わる。
コード中はそれぞれ、 address,name,age,testfor が変数として参照できる。
where: 無しだと、4つのテスト分それぞれメソッドを分けてかく必要がある。冗長化してメンテナンスが面倒になる。巨大なテストソースにありがちな構造。
コード中のキーワードの説明
- @Unroll
- setup:
- when:
- then:
このアノテーションがないと、テスト実行時に1つのテストとして扱われる。その場合失敗時にどのケースで失敗したか確認できない。
@Unrollを付けることを推奨。また、 #testfor のようにメソッド名にパラメタが使用できる。
ここに、試験に必要なデータをセットする。例えば、予め数件のレコードを入れておいて、正しく取得できるかのテストをしたい時用などデータを用意する場合に使う。
実際にテストを行う。
テストの結果を確認。
単純なデータではなく、オブジェクトなどを渡したい場合
オブジェクトを渡す場合は、配列にして渡してあげるとよい。
1 2 3 4 5 6 7 8 9 |
where: login << [new User(auth: "system") , new User(auth: "admin") , new User(auth: "user")] listsize << [5, 5, 3] total << [7, 5, 3] testas << ["system", "admin", "user"] |
テストソースはこのようになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
@Unroll def "listのテスト #testfor で行う"() { setup: new SomeData(data : "data1").save(flush: true) new SomeData(data : "data2").save(flush: true) new SomeData(data : "data3").save(flush: true) new SomeData(data : "data4").save(flush: true) new SomeData(data : "data5").save(flush: true) new SomeData(data : "data6").save(flush: true) new SomeData(data : "data7").save(flush: true) new SomeData(data : "data8").save(flush: true) new SomeData(data : "data9").save(flush: true) new SomeData(data : "data10").save(flush: true) def params = [max : 5] when: def listResult = dataServcie.list(user, params) then: listResult.list.size() == listsize listResult.total == total where: user << [new User(auth: "system") , new User(auth: "admin") , new User(auth: "user")] listsize << [5, 5, 3] total << [7, 5, 3] testfor << ["system", "admin", "user"] } |
dataServcie.list の結果としては
[list : データのリスト, total : 全件数]
が入っている。
このテストは、 User を system, admin, user の 3パターンでテストして結果としてリストの件数と、データの全件数を取得できるかチェックしている。
where: では、それぞれ行うテストのデータ(user)と結果(listsize, totla)、そして表示用(testfor)を用意している。
where: でオブジェクトを共有して使いまわす
where: で、ひとつのデータを共有して他のテストでも使いまわす場合は、クラスメンバとして宣言して
@Shared
を付ける。
「|」でパラメタ化も出来る。
クラスメンバとして宣言
1 2 3 4 5 6 |
@Shared User user1 @Shared User user2 |
setupSpec で初期化。 ちなみに、setupSpec はテスト中の初期化で一度だけ呼ばれる。 setup メソッドはテスト毎に呼ばれる。
1 2 3 4 5 6 |
def setupSpec() { user1 = new User("test1").save(flush : true) user2 = new User("test2").save(flush : true) } |
そして、パラメタ化で使う
1 2 3 4 5 6 |
where: user | age | address | testfor user1 | 10 | "user1@test.com" | "正常系 user1" user2 | 20 | "user2@test.com" | "正常系 user2" |
「|」と配列のパラメタ化がごっちゃ混ぜになってもよい。
1 2 3 4 5 6 7 8 9 |
where: user | age | address | testfor user1 | 10 | "user1@test.com" | "正常系 user1" user2 | 20 | "user2@test.com" | "正常系 user2" data << [new SomeData("data1") , new SomeData("data2") , new SomeData("data3") |