Java-SpockでIDEAはどの程度俺たちを助けてくれるのか

# はじめに

先日会社で「JavaのテストSpockで書くときに、IDEのリファクタツールってよしなにやってくれるんだっけ？」的トピックが出たので調べてみた話です。
間違っている部分や「そんな時はこうしたらいいよ！」みたいな話があればぜひ教えてください！！

# お膳立て： SpockでJavaのテストを書くということ
## そもそもJavaのテストを他言語で書くって？
Javaのテスティングフレームワークといえば、JUnitが有名ですが、メンテナンス性を考えると辛い場合に遭遇することがあります。
そこで、Javaで実装されたサービスでもテストケースは他の言語で書く、という選択肢が出てきます。
このあたりはまとまった議論の蓄積があり[^1]、その一例としてJavaのテストをSpockで書く、というトピックがあります。

## Spockとは
Groovyで実装されたテスティングフレームワークです。Groovyには一部熱烈なユーザーがいて[^2]、例えばJenkinsの父である川口さんは[「JUnitのテストをGroovyで書かないなんてもはやありえない」](http://d.hatena.ne.jp/kkawa/20121122)と言っています。

SpockはGroovyの機能をいっぱい利用し、JUnitに比べ簡潔な記述ができるようになっています。JavaのコードをSpockで書くということについては[Qiita記事](http://qiita.com/euno7/items/1e834d3d58da3e659f92)や[書籍](https://www.manning.com/books/java-testing-with-spock)なども出てきて、これからもっと増えていくんだろうなーと考えています。

- http://spockframework.org/

## IDEの力をどの程度借りられるか
さて、JavaのテストをGroovyで書くときに、IDEをどの程度味方につけられるか、というのが論点になります。
というのもGroovyは動的言語なので、メソッドシグニチャの自動変更が効かない場面が容易に想像できるからです[^3]。
今回はそれを調べてみましたレポートです。なお今回利用するIDEはIntelliJ IDEA 2016.3.4で試しています。

# Java-SpockをIDEAでごにょごにょする
## 今回利用するコード
今回は[TDDBCのgithubのコード](https://github.com/tddbc/groovy_spock)を使用していろいろ試してみます。

```java:プロダクションコードはJava
public class SampleOfJava {

    public String say() {
        return "Hello TDD BootCamp!";
    }

}
```

```groovy:テストコードはGroovy(Spock)
class SampleSpec extends Specification {

    def "should return 'Hello TDD BootCamp!' in Java"() {

        given:
        def sut = new SampleOfJava()

        when:
        String actual = sut.say()

        then:
        actual == 'Hello TDD BootCamp!'

    }

}
```
## メソッドのリネーム
まずはよく使うであろうメソッドのリネームです。`say`メソッドをリネームしてみましょう。`shift + F6`でいけます。

```java:リネーム
    public String shout() { // shoutにリネームしてみた
        return "Hello TDD BootCamp!";
    }
```
テストコードの方もちゃんと自動でリネームされてました。

```groovy
        given:
        def sut = new SampleOfJava()

        when:
        String actual = sut.shout() // リネームされた

        then:
        actual == 'Hello TDD BootCamp!'

```

まずは問題なし。

## シグニチャの変更
続いてシグニチャの変更試してみます。`command + F6`でリファクタ画面が開くので、そこから下記のように複数のパラメータを追加してみます。

|Type|Name|
|---|---|
|String|addedString|
|Integer|addedInteger|

プロダクションコードの方は当然シグニチャが変更され

```java:シグニチャの変更
    public String say(String addedString, Integer addedInteger) { // 引数を二つ追加
        return "Hello TDD BootCamp!";
    }
```
テストコードの方もシグニチャが変更されてました。でもちょっと丁寧ではない感じ。。。

```groovy
        when:
        String actual = sut.say(,) // 引数追加されてるけど、もう少しなんとかして欲しい気もする
```

これが気にくわない場合は、先ほどのリファクタリング画面で入力できる`Default value`を入れると少しはましになります。

```groovy
        when:
        String actual = sut.say("DEFAULT", i) // default valueを入れるともう少しそれっぽい
```

もう一声欲しいところですがまあよしとしましょう。

### 引数を1つだけ追加するときは注意
試していてトリッキーな部分に気がつきました。それはシグニチャ変更で引数を<b>1つだけ</b>増やす時。例えば下記のようにリファクタツールで引数を増やしてみると、プロダクションコードの方は変更されましたが

```java:シグニチャの変更（引数を一つだけ追加）
    public String say(String anotherParameter) { // 引数が追加された
        return "Hello TDD BootCamp!";
    }
```
テストコードの方はあれれ？？ 変更されませんでした。

```groovy
        when:
        String actual = sut.say() // 引数が増えてないorz
```

これ、恐ろしいことに書きようによってはテストも通ってしまうので曲者です。
なぜこんなことになるのかと言うと、Groovyでは引数１つのメソッドを呼び出す際、呼び出し元が引数を何もセットせずに呼び出してもそれはnullが自動的にセットされてよしなに計らってしまうためです[^4]。つまり先ほどのコードは

```groovy
        when:
        String actual = sut.say(null) // nullを自動で代入してる
```
と等価になります。
で、IDEAはこのことを知っていて、「これなら問題ないね」的勢いで呼び出し元の引数は追加しない、という形のようです。（もしかしたらバグかも知んないけど）
これは結構怖い。

#### 対策
というわけで対策ですが、
1. リファクタ時にdefault parameterを入れる
2. テスト側のクラスに `@CompileStatic`をつける

がありかなーと考えています。1.はすでに触れたので良しとして、2.についてもう少し。
Groovyは動的言語ですが、静的コンパイルするオプションも持っています。それが@CompileStaticというアノテーション。これをつければ、リファクタ系が仮にしくじっても、コンパイルエラーになるので気づけます。とはいえリファクタツールがちゃんと動くようになるわけではないので良い解決策ではないのですが。

# まとめ
IDEAで、Java-Spockの環境でごにょごにょしたところ、

- リネームはシグニチャ変更など、よく使うリファクタツールは基本問題なく使える
- 引数１つの時のシグニチャ変更はトリッキーなので注意

ということがわかった、というお話でした。個人的にはJava-Spockという構成超おすすめなのですが、やるなら細かいところ気をつけて使っていきましょう。

[^1]: 少し古いですが、 http://devtesting.jp/pekema/?0002%2FHotlinks がよくまとまっています。
[^2]: もちろん私もその一人です。
[^3]: なぜかというと、実行時まであるシグニチャのメソッドが呼ばれるかが決まらないためです。メタプログラミングできるから、実行直前に動的にメソッドが追加される、ということも概念的にはありうるので。
[^4]: 自分でもすっかり忘れてたのですが、この辺り昔Qiitaに書いたことがありました。http://qiita.com/PoohSunny/items/a1de52653d09133de4ad