SpockとS2JUnit4を混在できないのでspock-seasarを作った

# はじめに

SAStrutsなどSeasarベースのテストケースを作る時にS2JUnit4を使っているのですが、最近知ったSpockは簡潔に書くことができるので混ぜて使えないか試行錯誤していました。しかし、いくつか問題があり単純に混ぜることができませんでした。
そこでSpockの機能拡張を利用してS2JUnit4を取り込んだspock-seasarを作りました。

[Spock Framework - Seasar2 Module](https://bitbucket.org/shiena/spock-seasar)

# サンプル

[SAStrutsとJSON](http://qiita.com/shiena/items/146a0504c026ba00fbd0)で作ったJSONサンプルアプリのテストケース[AddActionTest.java](https://bitbucket.org/shiena/sample-sastruts-json-webapps/src/805111ae8392aa718a2eed081c86a623a376f6c6/src/test/java/sample/sastruts/json/webapps/action/AddActionTest.java)をspock-seasarを使って書き換えてみました。
spock-seasarを使うための設定は、テストクラスに`@Seasar2Support`アノテーションを付ける事だけです。
ここで`response`と`addAction`はS2JUnit4と同様に自動フィールドバインディングされています。
尚、`s2junit4.dicon`などを含んだファイル一式は[sample-sastruts-json-webapps](https://bitbucket.org/shiena/sample-sastruts-json-webapps)を参照してください。

```groovy:AddActionSpec.groovy
package sample.sastruts.json.webapps.action

import org.seasar.framework.mock.servlet.MockHttpServletResponse
import org.shiena.seasar.Seasar2Support
import spock.lang.Specification
import spock.lang.Unroll

import javax.servlet.http.HttpServletResponse

@Seasar2Support
class AddActionSpec extends Specification {
    HttpServletResponse response
    AddAction addAction

    @Unroll
    def '#a1 + #a2 = #json'() {
        setup:
        addAction.addForm.with {
            arg1 = a1
            arg2 = a2
        }

        when:
        def result = addAction.submit()
        def mockHttpServletResponse = response as MockHttpServletResponse

        then:
        result == null
        mockHttpServletResponse.contentType == 'application/json; charset=UTF-8'
        new String(mockHttpServletResponse.responseBytes, 'UTF-8') == json

        where:
        a1            | a2
        '1'           | '2'
        '-3'          | '2'
        '99999999999' | '1'
        '1'           | '99999999999'
        '-9999999999' | '1'
        '1'           | '-9999999999'
        'foobar'      | '1'
        '1'           | 'foobar'
        null          | '1'
        '1'           | null

        json << [
            '{"result":3,"messages":null}',
            '{"result":-1,"messages":null}',
            '{"result":null,"messages":["numeric value out of bounds (<9 digits>.<0 digits> expected)"]}',
            '{"result":null,"messages":["numeric value out of bounds (<9 digits>.<0 digits> expected)"]}',
            '{"result":null,"messages":["numeric value out of bounds (<9 digits>.<0 digits> expected)"]}',
            '{"result":null,"messages":["numeric value out of bounds (<9 digits>.<0 digits> expected)"]}',
            '{"result":null,"messages":["numeric value out of bounds (<9 digits>.<0 digits> expected)"]}',
            '{"result":null,"messages":["numeric value out of bounds (<9 digits>.<0 digits> expected)"]}',
            '{"result":null,"messages":["may not be empty"]}',
            '{"result":null,"messages":["may not be empty"]}',
        ]
    }
}
```

# spock-seasarの機能

基本的に[S2JUnit4](http://s2container.seasar.org/2.4/ja/S2JUnit4.html)と同じですが、追加および削除した機能があります。

## 追加した機能

Spockはgroovyで作られているので`TestContext.getMethodName()`はテストメソッド名ではなく、$spock_feature_0_0のような意図しない文字列を返却します。そのため、テストケースに記述したメソッド名を返却するTestContextの実装を用意しました。

### org.shiena.seasar.S2SpockInternalTestContextImpl

InternalTestContextImplを継承したTestContextです。

* getFeatureName() - Unroll前のテストメソッド名を返却します。
* getIterationName() - Unroll後のテストメソッド名を返却します。

### org.shiena.seasar.S2SpockSimpleInternalTestContext

SimpleInternalTestContextを継承したTestContextです。

* getFeatureName() - Unroll前のテストメソッド名を返却します。
* getIterationName() - Unroll後のテストメソッド名を返却します。

## 削除した機能

SpockとS2JUnit4で重複した機能やSpockで使いにくい機能は削除しました。

* メソッドの命名規則
* `@Prerequisite`アノテーション
* `@Mock`アノテーション
* `@Mocks`アノテーション
* `@EasyMock`アノテーション
* `@PostBindFields`アノテーション
* `@PreUnbindFields`アノテーション
* `s2junit4config.dicon`を使ったS2JUnit4のカスタマイズ

## 処理の順序

以下に、spock-seasarの処理順序と取り込んだ機能を太字で示します。
たとえば、自動フィールドバインディングは`setup()`の後なので、フィールドを参照できるのはテストメソッドの`setup`ブロック以降です。

1. setupSpec()
2. 各テストメソッドの繰り返し開始
3. **テスト用コンテナの初期化とTestContextのバインディング**
4. setup()
5. **自動フィールドバインディング**
6. テストメソッド実行
7. **自動フィールドバインディングしたフィールドの破棄**
8. cleanup()
9. **テスト用コンテナの破棄**
10. 各テストメソッドの繰り返し終了
11. cleanupSpec()

# 使い方

## gradleの場合

```groovy:build.gradle
repositories {
    maven {
        url 'https://bitbucket.org/shiena/mvn-repo/raw/tip/'
    }
}

dependencies {
    testCompile 'org.shiena:spock-seasar:0.0.1'
}
```

## mavenの場合

```xml:pom.xml
<repositories>
    <repository>
        <url>https://bitbucket.org/shiena/mvn-repo/raw/tip/</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>org.shiena</groupId>
        <artifactId>spock-seasar</artifactId>
        <version>0.0.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>
```

# 注意点

残念ながらSpockとS2JUnit4は必要なJUnitのバージョンが異なるので混ぜて使うことができません。
これが最初に書いたできなかった理由です。どちらかのバージョンに合わせても、もう片方でエラーが起きます。

* Spockは[JUnit4.7以上が必要です。](http://code.google.com/p/spock/wiki/SpockVersionsAndDependencies)
* S2JUnit4は[JUnit4.4が必要です。](http://s2container.seasar.org/2.4/ja/S2JUnit4.html#requisite)

# まとめ

Spockの機能拡張の作り方はSeasar2やSpring Frameworkのインターセプターと似た印象でした。
JUnitで共通処理を用意するときは親クラスを作ったりしましたが、Spockでは機能拡張で作ることもできそうです。

# 参考

* [Annotation Driven Extensions With Spock](http://ldaley.com/post/971946675/annotation-driven-extensions-with-spock)
* [Spock @RunJetty](http://tawus.wordpress.com/2012/08/03/spock-runjetty/)