Spockのドキュメントを読んだよ

社内の勉強会でなんかしゃべれーってことになり。なんとなくSpockがどっかで気になってたので、SpockどころかGroovyすら触ったことほぼないですけど、ドキュメントをサラーっと読んで面白いなーと思うところをSpock Web Consoleで動かしつつ紹介してみたりしました!復習がてらもういちど読んでみまする。

Spock Web Console

http://meetspock.appspot.com/

SpockBasics

https://code.google.com/p/spock/wiki/SpockBasics

Example

全体はこんな感じかな。読みやすいね。

class MyFirstSpecification extends Specification {
  def "pushing an element on the stack"() {
    setup:
      def stack = new Stack()
      def elem = "push me"

    when:
      stack.push(elem)

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

これをWebConsoleで実行してみるテスト!

MyFirstSpecification
 - pushing an element on the stack

失敗するようにしてみると。

      stack.size() == 2

こうなる

MyFirstSpecification
 - pushing an element on the stack   FAILED

   Condition not satisfied:
   
   stack.size() == 2
   |     |      |
   |     1      false
   [push me]
   
   at MyFirstSpecification.pushing an element on the stack(Script1.groovy:12)

キャーステキー!!!

てことで説明を読んでみる。

Blocks

6つのブロックがある。

  • setup:
  • when:
  • then:
  • expected:
  • cleanup:
  • where:
setup:

準備するとこ

setup:
def stack = new Stack()
def elem = "push me"
when: と then:

when: と then: はセットで使われる。when: でテストしたいことを実行して、then: でチェック。
then: のところはassertion使わなくても、bool値になってればいいみたいね。読み易いや。

when:
stack.push(elem)

then:
!stack.empty
stack.size() == 1
stack.peek() == elem
例外の場合

thrown()メソッドが使えると。

when:
stack.pop()

then:
thrown(EmptyStackException)
stack.empty

例外の内容をチェックしたい場合はこんなふうにできる。

when:
stack.pop()

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

もちょっと違う書き方だとこんな感じ。こっちのが型がはっきりしてるからIDEにやさしいよって。それと、前から読む感じになるので、読みやすいよって。僕もこっちのがなんとなく好きだな。

when:
stack.pop()

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

例外が投げられなかったことのチェックはこんな感じ

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

モックを使ったテストなんかも書けるよーって。詳細は http://docs.spockframework.org/en/latest/interaction_based_testing.html かな。

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")
}

publisher.fire("event")を呼び出したら、1回だけsubscriber1.receive("event")が実行されるよってことみたいね。
mockとかspyとかありそうなので、また今度チェックしてみよっと。

expected:

実行してチェックを1行で書いたほうが自然なときはwhen:とthen:じゃなくて、expected:が使えるよーって。例えばこういう場合は

when:
def x = Math.max(1, 2)

then:
x == 2

expected:使って一行で書くほうが分かり良いよね

expect:
Math.max(1, 2) == 2
cleanup:

後処理すね

setup:
def file = new File("/some/path")
file.createNewFile()

// ...

cleanup:
file.delete()

例外が投げられてもここは実行されるとのこと。いいなー。

where:

パラメタライズドテストで使うっぽいすね。これいいすね。さらっと http://docs.spockframework.org/en/latest/data_driven_testing.html を読んでみた。

例えばこういうテストをしたいとき

class MathSpec extends Specification {
    def "maximum of two numbers"() {
        expect:
        // exercise math method for a few different inputs
        Math.max(1, 3) == 3
        Math.max(7, 4) == 7
        Math.max(0, 0) == 0
    }
}

こう書けるす。

class Math extends Specification {
    def "maximum of two numbers"(int a, int b, int c) {
        expect:
        Math.max(a, b) == c

        where:
        a | b | c
        1 | 3 | 3
        7 | 4 | 4
        0 | 0 | 0
    }
}

読みやすくて素敵すなー。さらに、メソッドの引数は where:で分かるとのことで書かなくてもよいと。それと入力値と期待値の間はもう一個パイプを増やした方が分かり良いやろーって。こうなる

class DataDriven extends Specification {
     def "maximum of two numbers"() {
         expect:
         Math.max(a, b) == c

         where:
         a | b || c
         3 | 5 || 5
         7 | 0 || 7
         0 | 0 || 0
     }
 }

ちょっとのことだけど。そゆとこ気を遣ってるのいいねー。

パラメタライズドテストの実行結果

実行結果がどういう風に出力されるかってのを見たいので、失敗するようにしてみよう。

class DataDriven extends Specification {
     def "maximum of two numbers"() {
         expect:
         Math.max(a, b) == c

         where:
         a | b || c
         3 | 5 || 5
         7 | 0 || 5
         0 | 0 || 0
     }
 }

んで、WebConsoleで実行!

DataDriven
 - maximum of two numbers   FAILED

   Condition not satisfied:
   
   Math.max(a, b) == c
        |   |  |  |  |
        7   7  0  |  5
                  false
   
   at DataDriven.maximum of two numbers(Script1.groovy:4)

ふむふむ。ところで @Unroll というアノテーションをつけるとパラメータ毎に展開してくれるっぽい。やってみよう。

class DataDriven extends Specification {
     @Unroll
     def "maximum of two numbers"() {
         expect:
         Math.max(a, b) == c

         where:
         a | b || c
         3 | 5 || 5
         7 | 0 || 5
         0 | 0 || 0
     }
 }

結果はこうなった。なる。

DataDriven
 - maximum of two numbers[0]
 - maximum of two numbers[1]   FAILED

   Condition not satisfied:
   
   Math.max(a, b) == c
        |   |  |  |  |
        7   7  0  |  5
                  false
   
   at DataDriven.maximum of two numbers(Script1.groovy:5)

 - maximum of two numbers[2]

でも、パラメータの値をメソッド名に展開してくれる仕組みがなかったっけ?と思ってたら次に書いてあったわ。

class DataDriven extends Specification {
     @Unroll
     def "maximum of #a and #b is #c"() {
         expect:
         Math.max(a, b) == c

         where:
         a | b || c
         3 | 5 || 5
         7 | 0 || 5
         0 | 0 || 0
     }
 }

実行ー!

DataDriven
 - maximum of 3 and 5 is 5
 - maximum of 7 and 0 is 5   FAILED

   Condition not satisfied:
   
   Math.max(a, b) == c
        |   |  |  |  |
        7   7  0  |  5
                  false
   
   at DataDriven.maximum of #a and #b is #c(Script1.groovy:5)

 - maximum of 0 and 0 is 0

そうそう、これこれ。分かりやすいね。

Specifications as Documentation

ドキュメントとしての仕様か。

setup: "空の銀行口座を用意して"
// ...

when: "100円振り込むと"
// ...

then: "残高が100円になる"
// ...

てことで

Spockいいねー。

早速 Groovy-Eclipseだっけか?を入れてみたら何かとコンフリクトしてるっぽくて元々入ってたプラグインも出てこなくなったので、また今度やってみるー(ノД`)シクシク