Java のプログラムのテストを Groovy (spock) で書くのは昔からやっているのですが、テストデータを書き下す際の工夫をメモします。
テスト対象プログラム
package example;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
public class FirstDate {
/**
* テスト対象メソッド。
* 与えられた日付のリストから一番過去のものを返す
*/
public static Date firstDate(List<Date> dates) {
List<Date> copy = new ArrayList<>(dates);
copy.sort(Comparator.<Date>naturalOrder());
return copy.get(0);
}
}
この firstDate
メソッドをテストしたいとします。
package example
import spock.lang.Specification
class FirstDateSpec extends Specification {
// ここにテストメソッドを足していきます
}
ここにテストメソッドを足していきます。
普通のやり方
def "基本形"() {
given:
def dates = [new Date('2015/5/5'), new Date('2015/1/1'), new Date('2015/10/10'), new Date('2015/11/3')]
when:
def firstDate = FirstDate.firstDate(dates)
then:
firstDate == new Date('2015/1/1')
}
new Date
が沢山でてきてうっとおしいですね。この邪魔な new Date
を削減していくことを目指します。
new Date
を as Date
できるようにする
def setup() {
String.metaClass.define {
def originalAsType = String.metaClass.getMetaMethod('asType', [] as Class[])
asType = { Class c ->
if (c == Date) {
new Date(delegate)
} else {
originalAsType.invoke(delegate, c)
}
}
}
}
def "asで変換"() {
given:
def dates = ['2015/5/5' as Date, '2015/1/1' as Date, '2015/10/10' as Date, '2015/11/3' as Date]
when:
def firstDate = FirstDate.firstDate(dates)
then:
firstDate == '2015/1/1' as Date
}
Groovy パワーで String
を as
で Date
にできるようにしてみましたが、あんまり見た目は変わりませんね。
でもこの as は下準備です。
collect
で List<String>
を List<Date>
にする1
def "collectで変換1"() {
given:
def dates = ['2015/5/5', '2015/1/1', '2015/10/10', '2015/11/3'].collect { new Date(it) }
when:
def firstDate = FirstDate.firstDate(dates)
then:
firstDate == '2015/1/1' as Date
}
一気に邪魔者が減りましたね。
collect
で List<String>
を List<Date>
にする2
def "collectで変換2"() {
given:
def dates = ['2015/5/5', '2015/1/1', '2015/10/10', '2015/11/3'].collect { it as Date }
when:
def firstDate = FirstDate.firstDate(dates)
then:
firstDate == '2015/1/1' as Date
}
クロージャの中でも as を使ってみました。
collect
の変換クロージャを外出し
def "collectの変換クロージャを外出しして変換"() {
def asDate = { it as Date }
given:
def dates = ['2015/5/5', '2015/1/1', '2015/10/10', '2015/11/3'].collect asDate
when:
def firstDate = FirstDate.firstDate(dates)
then:
firstDate == '2015/1/1' as Date
}
クロージャを変数に入れてみました。List<Date>
を沢山作りたいときはちょっと簡潔になるかもしれません。なお、この例の場合でクロージャ部分が {new Date(it)}
だと it の型がわからないという警告が出ます。
static method で変換
static <T> List<T> memberAs(List<String> l, Class<T> c) {
return l.collect({ it.asType(c) })
}
def "staticMethodで変換"() {
given:
def dates = memberAs(['2015/5/5', '2015/1/1', '2015/10/10', '2015/11/3'], Date)
when:
def firstDate = FirstDate.firstDate(dates)
then:
firstDate == '2015/1/1' as Date
}
こういう書き方もできますが、memberAs の呼び出し方がなんとなくダサさを醸し出しています。
この呼び出しは Groovy のパワーでダサくなくす方法があります。
Category と use で変換
@Category(List)
class ListMemberAs {
public <T> List<T> memberAs(Class<T> c) {
return this.collect({ it.asType(c) })
}
}
def "Categoryで変換"() {
given:
List<Date> dates = null
use(ListMemberAs) {
dates = ['2015/5/5', '2015/1/1', '2015/10/10', '2015/11/3'].memberAs(Date)
}
when:
def firstDate = FirstDate.firstDate(dates)
then:
firstDate == '2015/1/1' as Date
}
Groovy の Category を使えばリストにメソッドを追加したように見せかけることができます。
ただし、呼び出ししている部分を use
とクロージャで囲う必要があり、 dates の宣言部分などにダサさが残ります。
なお given
, when
, then
を含む全体を use で囲うと spock のテストメソッドとして認識されなくなります。
この例にはそぐわないですが、Groovy の Category と use には可能性を感じます。
Category を Use アノテーションで適用
import spock.util.mop.Use
@Use(ListMemberAs)
def "CategoryをUseで適用して変換"() {
given:
List<Date> dates = ['2015/5/5', '2015/1/1', '2015/10/10', '2015/11/3'].memberAs(Date)
when:
def firstDate = FirstDate.firstDate(dates)
then:
firstDate == '2015/1/1' as Date
}
見た目的には完璧ですが、Intellij IDEA さんが、「List には memberAs なんてないよ?」という警告を出しやがります。また、dates に型の宣言 List<Date>
が必要になります。
ここを def
にすると「dates の型が特的できないよ?」という警告を出しやがります。
このテストでは問題がありませんが、Spock のデータテーブルを使いたい場合や、メソッドに直接渡したい場合など、型宣言ができない場合があります。そうすると dates
に関連する呼び出しで芋づる式に型推論が効かなくなって使い勝手が落ちてしまう場合があります。
現時点での自分の採用している方法
List<Date>
が沢山必要なプログラムのテストをしているので、collect asDate
による方法(collectの変換クロージャを外出し)を採用しています(実際のプロダクトでは Date じゃないクラスです)。
Groovy は Gradle と Spock にしか使ってない弱者なので、強い人が採用している方法を知りたいと思って記事を書いてみました。