Javaのテストでも使えるテーブルセットアップ用のGroovy DSL


> こちらの投稿のときに作成した、ライブラリは新たに`Hogen`という名前に変更になっています。
詳細に関しては近日公開させてもらうので、よろしければそちらを参照して頂けると幸いです。

最近プロダクションコードはJava、ビルドツール(Gradle)、テストコード(Spock、Geb)の組み合わを使うようになってGroovyを使う機会が増えてきました。

[JavaでのDBのテストデータ作成はDbSetupが楽
](http://qiita.com/ikemo/items/a27ece72fe2af3683ff6)
そんな中こちらの記事を拝見させてもらいました。
Groovyで似たようなinsertをしようとすると以下のようになります。

```Groovy:insert.groovy
Sql sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")

def table = sql.dataSet('item_master')
table.add(id:1, name:'Apple', price:500)
table.add(id:2, name:'Orange', price:250)
// ...

```

これでも十分分かりやすいのですが、どうせならSpockのデータテーブル構造にしたかったので、簡単なDSLを書いてみました。

使い方は以下のような感じです。

```Groovy:insert.groovy
Sql h2 = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")

TableSetup.insert h2, {
    table 'item_master'
    rows {
        id | name     | price
        1  | 'Apple'  | 500
        2  | 'Orange' | 250
    }
}
//  -> insert into item_master (id, name, price) values (1, Apple, 500)
//     insert into item_master (id, name, price) values (2, Orange, 250)
//     ...

```

個人差はあると思いますが、もとのaddメソッドより直感的にテーブルが記述出来ます。
[全てのソース:Github](https://github.com/disc99/table-setup)



## 仕組み

TableSetup#insertの第一引数にSqlインスタンスを渡し、それ以降はtableとrowsを定義したクロージャを可変長引数で渡せるようにしているので、続けてクロージャを渡せば複数テーブルにも対応します。

```Groovy:TableSetup.groovy
class TableSetup {

    def static insert(Sql sql, @DelegatesTo(TableInsertHandler) Closure... cls) {
        cls.each {
            def tableInsert = new TableInsertHandler(sql: sql)
            def handler = it.rehydrate(tableInsert, this, this)
            handler.resolveStrategy = Closure.DELEGATE_ONLY
            handler()
        }
    }

    static class TableInsertHandler {
        Sql sql
        String table
        def table(String table) { this.table = table }
        def rows(Closure cl) {
            TableParser.asTable(cl).toMapList().each {
                sql.dataSet(table).add(it)
            }
        }
    }
}

```


<br>
Spockで使われてるデータテーブル型はorの演算子オーバーロードを行いListに変換するような構造になっています。


```groovy:TableParser.groovy,PropertyColumnConverter.groovy,Row.groovy,Column.groovy
class TableParser {

    private static ThreadLocal<List> context = new ThreadLocal<List>()

    static or(self, arg) {
        appendRow(self, arg)
    }

    static appendRow(value, nextValue) {
        def row = new Row(values: [value])
        context.get().add(row)
        row.or(nextValue)
    }

    static asListOfRows(Closure tableData) {
        context.set([])
        use(TableParser) {
            tableData.delegate = new PropertyColumnConverter()
            tableData.resolveStrategy = Closure.DELEGATE_FIRST
            tableData()
        }
        context.get()
    }

    static asTable(Closure tableData) {
        def list = asListOfRows(tableData)
        new Table(list.head().values, list.tail())
    }
}

class PropertyColumnConverter {

    def getProperty(String property) {
        new Column(name: property)
    }
}

@ToString
class Column {
    String name
}

@ToString
class Row {

    List values = []

    def or(arg) {
        values.add(arg)
        this
    }
}

```

Groovyは最近使い始めたところですがJavaと比べてシンプルな記述や、メタプログラミングのしやすさなどこういった時に非常に便利で楽しいですね！



### 参考
[Domain-Specific Languages](http://docs.groovy-lang.org/docs/latest/html/documentation/core-domain-specific-languages.html)
[Simple Table DSL in Groovy](http://tux2323.blogspot.jp/2013/04/simple-table-dsl-in-groovy.html)