はじめに
先日会社で「JavaのテストSpockで書くときに、IDEのリファクタツールってよしなにやってくれるんだっけ?」的トピックが出たので調べてみた話です。
間違っている部分や「そんな時はこうしたらいいよ!」みたいな話があればぜひ教えてください!!
お膳立て: SpockでJavaのテストを書くということ
そもそもJavaのテストを他言語で書くって?
Javaのテスティングフレームワークといえば、JUnitが有名ですが、メンテナンス性を考えると辛い場合に遭遇することがあります。
そこで、Javaで実装されたサービスでもテストケースは他の言語で書く、という選択肢が出てきます。
このあたりはまとまった議論の蓄積があり1、その一例としてJavaのテストをSpockで書く、というトピックがあります。
Spockとは
Groovyで実装されたテスティングフレームワークです。Groovyには一部熱烈なユーザーがいて2、例えばJenkinsの父である川口さんは「JUnitのテストをGroovyで書かないなんてもはやありえない」と言っています。
SpockはGroovyの機能をいっぱい利用し、JUnitに比べ簡潔な記述ができるようになっています。JavaのコードをSpockで書くということについてはQiita記事や書籍なども出てきて、これからもっと増えていくんだろうなーと考えています。
IDEの力をどの程度借りられるか
さて、JavaのテストをGroovyで書くときに、IDEをどの程度味方につけられるか、というのが論点になります。
というのもGroovyは動的言語なので、メソッドシグニチャの自動変更が効かない場面が容易に想像できるからです3。
今回はそれを調べてみましたレポートです。なお今回利用するIDEはIntelliJ IDEA 2016.3.4で試しています。
Java-SpockをIDEAでごにょごにょする
今回利用するコード
今回はTDDBCのgithubのコードを使用していろいろ試してみます。
public class SampleOfJava {
public String say() {
return "Hello TDD BootCamp!";
}
}
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
でいけます。
public String shout() { // shoutにリネームしてみた
return "Hello TDD BootCamp!";
}
テストコードの方もちゃんと自動でリネームされてました。
given:
def sut = new SampleOfJava()
when:
String actual = sut.shout() // リネームされた
then:
actual == 'Hello TDD BootCamp!'
まずは問題なし。
シグニチャの変更
続いてシグニチャの変更試してみます。command + F6
でリファクタ画面が開くので、そこから下記のように複数のパラメータを追加してみます。
Type | Name |
---|---|
String | addedString |
Integer | addedInteger |
プロダクションコードの方は当然シグニチャが変更され
public String say(String addedString, Integer addedInteger) { // 引数を二つ追加
return "Hello TDD BootCamp!";
}
テストコードの方もシグニチャが変更されてました。でもちょっと丁寧ではない感じ。。。
when:
String actual = sut.say(,) // 引数追加されてるけど、もう少しなんとかして欲しい気もする
これが気にくわない場合は、先ほどのリファクタリング画面で入力できるDefault value
を入れると少しはましになります。
when:
String actual = sut.say("DEFAULT", i) // default valueを入れるともう少しそれっぽい
もう一声欲しいところですがまあよしとしましょう。
引数を1つだけ追加するときは注意
試していてトリッキーな部分に気がつきました。それはシグニチャ変更で引数を1つだけ増やす時。例えば下記のようにリファクタツールで引数を増やしてみると、プロダクションコードの方は変更されましたが
public String say(String anotherParameter) { // 引数が追加された
return "Hello TDD BootCamp!";
}
テストコードの方はあれれ?? 変更されませんでした。
when:
String actual = sut.say() // 引数が増えてないorz
これ、恐ろしいことに書きようによってはテストも通ってしまうので曲者です。
なぜこんなことになるのかと言うと、Groovyでは引数1つのメソッドを呼び出す際、呼び出し元が引数を何もセットせずに呼び出してもそれはnullが自動的にセットされてよしなに計らってしまうためです4。つまり先ほどのコードは
when:
String actual = sut.say(null) // nullを自動で代入してる
と等価になります。
で、IDEAはこのことを知っていて、「これなら問題ないね」的勢いで呼び出し元の引数は追加しない、という形のようです。(もしかしたらバグかも知んないけど)
これは結構怖い。
対策
というわけで対策ですが、
1. リファクタ時にdefault parameterを入れる
2. テスト側のクラスに @CompileStatic
をつける
がありかなーと考えています。1.はすでに触れたので良しとして、2.についてもう少し。
Groovyは動的言語ですが、静的コンパイルするオプションも持っています。それが@CompileStaticというアノテーション。これをつければ、リファクタ系が仮にしくじっても、コンパイルエラーになるので気づけます。とはいえリファクタツールがちゃんと動くようになるわけではないので良い解決策ではないのですが。
まとめ
IDEAで、Java-Spockの環境でごにょごにょしたところ、
- リネームはシグニチャ変更など、よく使うリファクタツールは基本問題なく使える
- 引数1つの時のシグニチャ変更はトリッキーなので注意
ということがわかった、というお話でした。個人的にはJava-Spockという構成超おすすめなのですが、やるなら細かいところ気をつけて使っていきましょう。
-
少し古いですが、 http://devtesting.jp/pekema/?0002%2FHotlinks がよくまとまっています。 ↩
-
もちろん私もその一人です。 ↩
-
なぜかというと、実行時まであるシグニチャのメソッドが呼ばれるかが決まらないためです。メタプログラミングできるから、実行直前に動的にメソッドが追加される、ということも概念的にはありうるので。 ↩
-
自分でもすっかり忘れてたのですが、この辺り昔Qiitaに書いたことがありました。http://qiita.com/PoohSunny/items/a1de52653d09133de4ad ↩