21
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Groovy (Spock) で Java のテストをするときのテストデータ作成のTips

Last updated at Posted at 2015-09-16

Java のプログラムのテストを Groovy (spock) で書くのは昔からやっているのですが、テストデータを書き下す際の工夫をメモします。

テスト対象プログラム

FirstDate.java
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 メソッドをテストしたいとします。

FirstDateSpec.groovy
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 Dateas 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 パワーで StringasDate にできるようにしてみましたが、あんまり見た目は変わりませんね。
でもこの as は下準備です。

collectList<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
}

一気に邪魔者が減りましたね。

collectList<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 にしか使ってない弱者なので、強い人が採用している方法を知りたいと思って記事を書いてみました。

21
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?