More Related Content Similar to Introduction to Spock Similar to Introduction to Spock (20) Introduction to Spock2. お前誰よ
• 名前:杉浦孝博
• twitter : @touchez_du_bois
• 昼のお仕事:
Grailsアプリ、C#アプリ、VB.NETアプリの保守
とあるサイトの運用、保守
時々開発
6. 本日の内容
• 入門編
• Spockとは
• Spockの利点
• 構造
• Power Assert
• データ駆動テスト
• 相互作用中心のテスト
7. 本日の内容
• その他
• Spockの拡張機能
• Spockのモジュール
• Spockと一緒に
• まとめ
10. Spockとは
• Java, Groovy向けの、テスト・仕様
フレームワーク。
• たいていのIDE、ビルドツール、CI
サーバで使用可能。
17. Specification
• 対象クラスのテスト内容・仕様を記述。
• spock.lang.Specificationクラスを継
承。
import spock.lang.*
!
class TargetClassSpec extends Specification {
// Fields
// Fixture Methods
// Feature Methods
// Helper Methods
}
18. Fields
• テストで使用するオブジェクトを宣言。
• Featureメソッド実行ごとに初期化され
るため、基本的に、インスタンス変数は
Featureメソッド間で共有できない。
• 共有したい場合、@Sharedを付ける。
@Shared
def stack = new Stack()
19. Fixture Methods
• テストの準備(setup)、後始末(cleanup)
を行う。
def setup() {} // 各Featureメソッドの実行前に実行
def cleanup() {} // 各Featureメソッドの実行後に実行
def setupSpec() {} // 最初のFeatureメソッドの実行前に実行
def cleanupSpec() {} // 最後のFeatureメソッドの実行後に実行
20. Feature Methods
• テストの内容、仕様の内容を記述。
• 4つのフェーズからなり、それぞれBlock
を記述。
• Setup(前処理、前提条件)
• Stimulus(実行)
• Response(検証)
• Cleanup(後処理)
22. BlockとPhase
Block Phase
setup: / given: Setup
when: Stimulus
then: Response
expect: Stimulus + Response
cleanup: cleanup
where: -
and: -
25. Helper Methods
• Featureメソッドが大きくなった場合
に、意味がある内容をメソッド化する。
• setupやcleanupも処理が大きくなった
場合に、メソッド化する。
28. Power Assert
• テスト・仕様の内容を満たさない場合、
各部分式の値を表示する。
• SpockからGroovy本体に取り込まれ
た。
⇒ どこが違うか、どこから違うか、
ということが、比較的わかりやすい。
29. Power Assert
def "maximum of two numbers"() {
setup:
def a = 2
def b = 1
def c = 1
expect:
Math.max(a, b) == c
}
31. Power Assert
@Unroll
def "str1 == str2"() {
setup:
def str1 = "abcdefghijklmnopqrstuvwxyz"
def str2 = "abcdefghijk1mnopqrstuvwxyz"
expect:
str1 == str2
}
32. Power Assert
str1 == str2
| | |
| | abcdefghijk1mnopqrstuvwxyz
| false
| 1 difference (96% similarity)
| abcdefghijk(l)mnopqrstuvwxyz
| abcdefghijk(1)mnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
37. データテーブル
• コードとデータを分離。
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b | c // データ変数
1 | 3 | 3 // データ行
7 | 4 | 7 // 〃
0 | 0 | 0 // 〃
}
// Math.max(1, 3) == 3
// Math.max(7, 4) == 7
// Math.max(0, 0) == 0
// の3イテレーション実施。
38. データテーブル
• 入力値と期待値を(多少)わかりやすく。
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b || c // データ変数
1 | 3 || 3 // データ行
7 | 4 || 7 // 〃
0 | 0 || 0 // 〃
}
39. どこが失敗?
• 何回目のイテレーションで失敗したか
わからない。
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3
7 | 4 || 4
0 | 0 || 0
}
maximum of two numbers FAILED
!
Condition not satisfied:
!
Math.max(a, b) == c
| | | | |
7 7 4 | 4
false
40. @Unroll
• @Unrollを付けたメソッドまたはクラス
は、イテレーション毎にレポートされる。
@Unroll
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3
7 | 4 || 4
0 | 0 || 0
}
maximum of two numbers[0] PASSED
maximum of two numbers[1] FAILED
!
Math.max(a, b) == c
| | | | |
7 7 4 | 4
false
!
maximum of two numbers[2] FAILED
41. もう少しわかりやすく
• メソッド名にプレースホルダを使うこと
で、レポート結果がわかりやすく。
@Unroll
def "maximum of #a and #b is #c"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3
7 | 4 || 4
0 | 0 || 0
}
maximum of 1 and 3 is 3 PASSED
maximum of 7 and 4 is 4 FAILED
!
Math.max(a, b) == c
| | | | |
7 7 4 | 4
false
!
maximum of 0 and 0 is 0 FAILED
42. データパイプ
• データ変数ごとにデータプロバイダを用
意し、<<演算子で接続する。
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a << [ 1, 7, 0 ]
b << [ 3, 4, 0 ]
c << [ 3, 7, 0 ]
}
// Collectionだけでなく、テキストファイルやデータベースなど、
// 外部リソースから取得することも可能。
43. データパイプで複数の値
• データプロバイダが複数の値を返す場合、
複数のデータ変数に同時に接続可能。
@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")
!
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
[a, b, c] << sql.rows("select a, b, c from maxdata")
}
44. データ変数へ代入
• データ変数に直接値を代入することも可
能。
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a = 3
b = Math.random() * 100
c = a > b ? a : b
}
47. 相互作用中心のテスト
• Publish - Subscribeのように、
オブジェクト同士の振る舞いをテスト・
仕様化したい場合。
• 相互作用相手について、
本物のオブジェクトを使うこともあるが、
モックオブジェクトを使うこともある。
50. プログラム例
class Publisher {
List<Subscriber> subscribers = []
void send(String message) {
subscribers.each {
it.receive(message)
}
}
void send(Event event) {
subscribers.each {
it.receive(event)
}
}
}
!
interface Subscriber {
void receive(String message)
void receive(Event event)
}
51. モックオブジェクトの作成
• Mockメソッドで作成
// モックオブジェクトのインタフェース(クラス)を指定して作成
def subscriber = Mock(Subscriber)
!
// 変数の型からモックオブジェクトのインタフェース(クラス)の
// 型を推論
Subscriber subscriber = Mock()
52. モックオブジェクトの準備
• 例えば、こんな感じで。
class PublisherSpec extends Specification {
Publisher publisher = new Publisher()
Subscriber subscriber1 = Mock()
Subscriber subscriber2 = Mock()
!
def setup() {
publisher.subscribers << subscriber1
publisher.subscribers << subscriber2
}
}
53. モッキング
• テスト/仕様対象のオブジェクトと、相互
作用するオブジェクトの間の、インタラ
クション(相互作用)を宣言すること。
def "should send messages to all subscribers"() {
when:
publisher.send("hello")
!
then:
1 * subscriber1.receive("hello")
1 * subscriber2.receive("hello")
}
54. インタラクション
• 多重度(cardinality)、対象制約(target
constraint)、メソッド制約(method
constraint)、引数制約(argument
constraint)からなる。
1 * subscriber.receive("hello")
| | | |
| | | argument constraint
| | method constraint
| target constraint
cardinality
55. 多重度
• 何回メソッド呼び出しが行われるかを指
定する。
• 固定の数値や範囲を指定可能。
1 * subscriber.receive("hello") // 1回
0 * subscriber.receive("hello") // 0回
(1..3) * subscriber.receive("hello") // 1〜~3回
(1.._) * subscriber.receive("hello") // 1回以上
(_..3) * subscriber.receive("hello") // 3回以下
_ * subscriber.receive("hello") // 0回以上
56. 対象制約
• どのオブジェクト(モックオブジェクト)
を対象にするかを指定する。
1 * subscriber1.receive("hello") // subscriber1に対して
1 * _.receive("hello") // 任意のモックオブジェクト
57. メソッド制約
• どのメソッド呼び出しを対象にするかを
指定する。
1 * subscriber.receive("hello") // receiveメソッド
1 * subscriber._("hello") // 任意のメソッド
1 * subscriber./r.*e/("hello") // rで始まりeで終わるメソッド
58. 引数制約
• どんな引数を期待するかを指定する。
// 引数が"hello"という文字列
1 * subscriber.receive("hello")
// 引数が"hello"という文字列ではない
1 * subscriber.receive(!"hello")
// 引数なし
1 * subscriber.receive()
// 1引数(nullも含む)
1 * subscriber.receive(_)
// 任意の引数
1 * subscriber.receive(*_)
// nullではない
1 * subscriber.receive(!null)
// String型の引数(nullは含まれない)
1 * subscriber.receive(_ as String)
// 指定された条件を満たす引数
1 * subscriber.receive({ it.size() > 3 })
60. スタビング
• モッキングに比べ、多重度の指定がない
代わりに、レスポンスジェネレータ
(Response Generator)を指定する。
subscriber1.receive(_) >> "ok"
| | | |
| | | response generator
| | argument constraint
| method constraint
target constraint
61. スタビングの例
interface Subscriber {
def setup() {
publisher.subscribers << subscriber1
publisher.subscribers << subscriber2
!
// receiveメソッドの戻り値は常に"ok"とする
subscriber1.receive(_) >> "ok"
subscriber2.receive(_) >> "ok"
}
// String型の値を返すように変更
String receive(String message)
String receive(Event message)
}
62. 一連の値を返す
• >>>演算子の後に、リストなどイテレー
ティブな値を指定する。
// 1回目: "ok"
// 2回目: "error"
// 3回目: "error"
// 4回目以降: "ok"
subscriber1.receive(_) >>> ["ok", "error", "error", "ok"]
64. 副作用の実行
• >>演算子の後のクロージャ中に、副作用
としての処理を記述する。
subscriber1.receive(_) >> { String message ->
println message
throw new InvalidArgumentException()
}
66. スタブオブジェクトの作成
• Stubメソッドで作成する。
• Mockメソッドで作成した場合、モッキ
ングもスタビングもできるが、Stubメ
ソッドで作成した場合はスタビングのみ。
// スタブオブジェクトのインタフェース(クラス)を指定して作成
def subscriber = Stub(Subscriber)
!
// 変数の型からスタブオブジェクトのインタフェース(クラス)の
// 型を推論
Subscriber subscriber = Stub()
69. スパイでのスタビング
• スパイで普通にスタビングを行うと、本
物のメソッドが呼ばれなくなる。
// スパイ対象のクラスを指定して作成
def subscriber =
Spy(SubscriberImpl, constructorArgs: ["Fred"])
!
// スタビング。本物のreceiveメソッドは呼ばれない。
subscriber.receive(_) >> "ok"
70. スタビング+本物のメソッド
• callRealMethodメソッド、あるいは
callRealMethodWithArgsメソッドを使
うことで、任意のコードの実行と、本物
のメソッドへの委譲が行われる。
// 本物のreceiveメソッドが呼ばれた後、
// 戻り値を動的に変更
subscriber.receive(_) >> { String message ->
callRealMethod()
message.size() > 3 ? "ok" : "fail"
}
71. スタビング+本物のメソッド
• callRealMethodメソッドと
callRealMethodWithArgsメソッドの違
いは、後者は呼び出し時の引数を差し替
えたい場合に使用する。
// 本物のreceiveメソッドを、違う引数で実行し、
// 戻り値を動的に変更
subscriber.receive(_) >> { String message ->
callRealMethodWithArgs("Changed message")
message.size() > 3 ? "ok" : "fail"
}
79. 純正なもの
• spock-unitils
• Unitilsというテスト用ユーティリ
ティをSpockと統合したもの。
• spock-spring, spock-boot
• それぞれ、Spring TestContext
やSpring Bootと統合したもの。
80. 純正でないもの
• Spock Report Extension
• 実行結果のレポートを作成するた
めのグローバル拡張機能。
• https://github.com/
renatoathaydes/spock-reports
83. Geb + Spock
• Gebとは、Webブラウザを使うWeb
アプリケーションの操作を自動化・
自動実行するためのソフトウェア。
• Geb用のSpecificationクラスを提供。
84. Arquillian + Spock
• Arquillianとは、自動テストでJavaEE
コンテナを利用可能にするためのソ
フトウェア。
• ArquillianをSpockと組み合わせて使
えるように、アノテーションなど機
能拡張を提供。
88. 参考URL
• 本家
• https://code.google.com/p/spock/
• ソースコード
• https://github.com/spockframework/spock
• リファレンスドキュメント(英語版)
• http://spock-framework.readthedocs.org/en/latest/
• リファレンスドキュメント(日本語版)
• http://spock-framework-reference-documentation-ja.readthedocs.org/ja/latest/
• G*ワークショップZ May 2013 - Spockハンズオンの資料
• https://github.com/spockframework/spock
89. 参考URL
• Geb
• http://www.gebish.org/
• Arquillian
• http://arquillian.org/
• Arquillian TestRunner Spock
• http://arquillian.org/modules/spock-test-runner/
• RoboSpock
• http://robospock.org/