BDDフレームワーク『Spock』公式文書読解メモ

2013/03/17(日)に開催された『Yokohama.groovy #13』での個人的取り組みアウトプットその2。

Spockについては実際に導入し動かしてはいたものの、仕様や仕組みの詳細まではあんま踏み込んでなかったな〜と思い、この時間を使って実践・読解してみました。その結果をエントリとしてUP。

対象となるページは、以下公式サイト。


なお、Spockについては以下のエントリで日本語翻訳がされております。『こっち見りゃ良いじゃん』とも思いましたが、ここは敢えて自分で原文を読みながら、コード書きながら理解を深めるという事で見ずに読解を進めてました。多分自分のメモは粗だらけなので、後でこちらの翻訳も見なおして理解を正そうと思います。:-)

以下読解時のメモ。

インポート
import spock.lang.*

Spcificationを利用する際に必須。

Specification(仕様)
  • spock.lang.Specificationクラスを拡張して表現。
  • システムやシステムの操作に纏わる名称が付けられる。
  • Specificationクラスは仕様を記述する際の幾つかの便利なメソッドを有している。
  • また、SpockはIDEやビルドツールでも実行が可能。
フィールド
  • インスタンスフィールドは仕様のフィクスチャに属するオブジェクトを格納するのに良い場所。
  • 宣言時に初期化するのも良いプラクティス。
  • メソッド間で共有はしない。その代わり、メソッド毎にオブジェクトを有する。これはメソッド毎が独立しているという事を示している。
@Shared res = new VeryExpensiveResource()

オブジェクトを共有したい場合は@Sharedアノテーションを付加。

static フィールドにした場合、定数としてしか使えなくなる。
フィクスチャメソッド
def setup() {}          // メソッド実行前に実施される(※メソッド単位)
def cleanup() {}        // メソッド実行後に実施される(※メソッド単位)
def setupSpec() {}     // 最初のフィーチャーメソッド実行前に実施される(※クラス単位)
def cleanupSpec() {}   // 最後のフィーチャーメソッド実行後に実施される(※クラス単位)
フィーチャーメソッド
  • 仕様の中心・心臓部。
  • 仕様下で、あなたが期待したい事を記述する。
  • フィーチャーメソッドは文字列で記載。良い名前を付けられるようにチャレンジしてみよう。
  • コンセプトとしては、フィーチャーメソッドは以下4つのフェーズから成る。
    • フィーチャーのフィクスチャを準備
    • 仕様下でシステムに刺激を与える
    • システムからの反応/期待値を記述する
    • フィーチャーのフィクスチャを後始末
ブロック
  • フィーチャーメソッドはブロックと呼ばれるもので構成されている。
  • ブロックには6つの種類があり、フィーチャーで少なくとも1つは持っていなければならない。
    • setup
    • when
    • then
    • expect
    • cleanup
    • where
  • 以下画像は構造を図示したもの。whereだけ若干特殊。
ブロック:setupブロック
setup:
def stack = new Stack()
def elem = "push me"

事前準備用。

ブロック:when/thenブロック
when:
	// 〜〜した時に(刺激)
then:
    // 〜〜である事(反応)
  • then/whenは必ず対になる。また、フィーチャーメソッド内でthen/whenの対を復数記述する事も出来る。
  • 条件(Condition):
    • JUnitのassertionのようなものを期待すると思うが、ここでの条件(Conditoin)はBoolean型の表現(正しいか正しくないか)を意味する。
when:
stack.push(elem)

then:
!stack.empty
stack.size() == 1
stack.peek() == elem
    • また、Spockはあらゆる値をキャプチャし、とても分かり易い形式で情報を提示してくれる、ナイスな奴である。
Condition not satisfied:

stack.size() == 2
|     |      |   
|     1      false
[push me]
  • 暗黙的及び明示的な条件(Implicit and explicit conditions)
    • 条件(Condition)はthen/expectブロックで必須の要素。
    • 上記以外で条件を判定したい場合は、Groovyのassertキーワードを使って以下のように書く事が出来る。
def setup() {
  stack = new Stack()
  assert stack.empty
}
  • 例外条件(Exception Conditions)
    • 処理実行時に例外が発生する事を検証したい場合、thrownメソッドを使って以下のように書く事が出来る。
when:
stack.pop()

then:
thrown(EmptyStackException)
stack.empty
  • 例外要素にアクセスして検証する場合のまず最初の方法は変数として用意する事。
when:
stack.pop()

then:
def e = thrown(EmptyStackException)
e.cause == null

また、以下のように書くことも出来る。

when:
stack.pop()

then:
EmptyStackException e = thrown()
e.cause == null
  • 例外が投げられてない事を検証したい場合、notThrown()メソッドを用いて以下のように書く事も出来る。
def "HashMap accepts null key"() {
  setup:
  def map = new HashMap()
  
  when:
  map.put(null, "elem")
  
  then:
  notThrown(NullPointerException)
}
ブロック:expectブロック
  • whenブロックで行う"刺激"(〜〜した時に)とthenブロックで行う"反応"(〜〜である事)を1つの表現で確認する事が出来る便利なもの。
  • when/thenブロックだとこう書いていたものが、
when:
def x = Math.max(1, 2)

then:
x == 2
  • expectブロックだとこう書ける。
expect:
Math.max(1, 2) == 2
ブロック:cleanupブロック
  • 後始末、お掃除用。
setup:
def file = new File("/some/path")
file.createNewFile()

// ...

cleanup:
file.delete()
ブロック:whereブロック
  • whereブロックは常にメソッドの最後にくる。そしてもしかしたら繰り返し実行されないかもしれない。
  • データ・ドリブンメソッドとして実行される。
  • 以下コード例。
def "computing the maximum of two numbers"() {
  expect:
  Math.max(a, b) == c

  where:
  a << [5, 3]
  b << [1, 9]
  c << [5, 9]
}


また、公式サイト内の他のトピックについても興味深そうなものを以下に列挙。