Tbpgr Blog

Recruiting Operations tbpgr(てぃーびー) のブログ

フィーチャ・メソッド Feature Methods

概要

BDDフレームワークSpockの基礎情報
フィーチャ・メソッド Feature Methodsについて

詳細

フィーチャ・メソッド

フィーチャメソッドは文字列リテラルからなるフィーチャ・メソッド名とブロック要素で構成されます

■フィーチャ・メソッド

def "pushing an element on the stack"() {
  // blocks go here
}
def "スタックにエレメントをプッシュする"() {
  // blocks go here
}

フィーチャ・メソッドのフロー

フィーチャ・メソッドは下記の4つのフローがあります

順序 フロー内容 対応するブロック要素
1 Setup フィーチャのフィクスチャ設定 setup
2 Stimulus 仕様対象システムの刺激を用意する when,expect
3 Response システムに期待されるレスポンスを記述する then,expect
4 Cleanup フィーチャの後始末をする cleanup
−− where
Setup ブロック
setup:
def stack = new Stack()
def elem = "push me"

フィーチャの設定を行う為に一回だけ記述される。
省略可能。aliasとしてgivenを使用することも可能。

When/Then ブロック
when:   // stimulus
then:   // response

when/thenは常にペアで使用される。
whenには任意のコードを記述可能。
thenには条件 conditions, 例外条件 exception conditions, インタラクション interactions と変数定義のみ記述可能。
フィーチャ・メソッドには複数のwhenーthenを記述可能

■条件
JUnitと異なり、条件はただの真偽値を表す式で記述します。

when:
stack.push(elem)

then:
!stack.empty
stack.size() == 1
stack.peek() == elem

■暗黙的条件と明示的条件 Implicit and explicit conditions
ブロックのトップレベルの条件は暗黙的条件として真偽値のみ指定すればよいが、
その他の場所で利用する場合は、明示的にassertを指定する必要があります。

def setup() {
  stack = new Stack()
  assert stack.empty
}

■例外条件 Exception Conditions
例外条件はwhenブロックが例外を投げるべき時に使用されます。
thrownに予想される例外を記述します。

when:
stack.pop()

then:
thrown(EmptyStackException)
stack.empty

例外条件の後には他の条件などを続けて書くことが出来る為、例外の内容の指定をする場合などに有用です。

when:
stack.pop()

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


例外が起こらないことを確認することも出来ます

def "HashMap accepts null key"() {
  setup:
  def map = new HashMap()
  
  when:
  map.put(null, "elem")
  
  then:
  notThrown(NullPointerException)
}

■インタラクション Interactions
複数のオブジェクトがどのように関わりあうかを表します

def "events are published to all subscribers"() {
  def subscriber1 = Mock(Subscriber)
  def subscriber2 = Mock(Subscriber)
  def publisher = new Publisher()
  publisher.add(subscriber1)
  publisher.add(subscriber2)
  
  when:
  publisher.fire("event")
  
  then: 
  1 * subscriber1.receive("event")
  1 * subscriber2.receive("event")
}
Expect ブロック

expectブロックは条件と変数定義のみを書いて、刺激と応答の両方を記述します。

expect:
Math.max(1, 2) == 2
Cleanup ブロック
setup:
def file = new File("/some/path")
file.createNewFile()

// ...

cleanup:
file.delete()

フィーチャのリソース開放のために一回だけ記述される。

Where ブロック

常にメソッドの最後に記述し、1回だけ記述できます。

def "computing the maximum of two numbers"() {
  expect:
  Math.max(a, b) == c

  where:
  a << [5, 3]
  b << [1, 9]
  c << [5, 9]
}
ヘルパー・メソッド

何度も使用する条件の共通化などを行う際にヘルパー・メソッドとして処理を切り出すことができます。
ただし、Spockの親切で分かりやすいエラー出力を利用する場合は切り出したメソッドの中で
assertを行うようにコードを修正する必要があります。

以下、公式サイトのコード例
■修正前

def "offered PC matches preferred configuration"() {
  when:
  def pc = shop.buyPc()
  
  then:
  pc.vendor == "Sunny"
  pc.clockRate >= 2333
  pc.ram >= 4096
  pc.os == "Linux"
}

■修正後1

def "offered PC matches preferred configuration"() {
  when:
  def pc = shop.buyPc()
  
  then:
  matchesPreferredConfiguration(pc)
}

def matchesPreferredConfiguration(pc) {
  pc.vendor == "Sunny"
  && pc.clockRate >= 2333
  && pc.ram >= 4096
  && pc.os == "Linux"
}

この記述だとエラー出力が以下のようになり、分かりにくい

Condition not satisfied:

matchesPreferredConfiguration(pc)
|                             |
false                         ...

■再修正後
ヘルパーメソッドを以下のようにすると分かりやすい出力になります

void matchesPreferredConfiguration(pc) {
  assert pc.vendor == "Sunny"
  assert pc.clockRate >= 2333
  assert pc.ram >= 4096
  assert pc.os == "Linux"
}

以下、出力内容

Condition not satisfied:

assert pc.clockRate >= 2333
       |  |         |
       |  1666      false
       ...

■ドキュメントとしての仕様
各ブロックの後にテキスト記述を追加して仕様を分かりやすくすることができます。

setup: "open a database connection"
// code goes here

and: "seed the customer table"
// code goes here

and: "seed the product table"
// code goes here