ビルド・リリース自動化への足掛かりとしてのGroovy・Gradle・Spock | サイバーエージェント 公式エンジニアブログ
はじめまして!Ameba事業本部プラットフォームDivでアプリケーションエンジニアをしている池田(@yukung)です。
私は2011年5月に大手SIerから転職し、サイバーエージェントへ入社しました。前職では主に金融業界をターゲットにしたBtoB向けのシステム開発に多く携わってきましたが、開発作業の中でこんな経験を割とたくさんしてきました。

・ソースコードやドキュメントの管理にバージョン管理ツールを使わずに、Hoge_yyyyMMdd.javaとかDB設計_yyyyMMdd.xlsみたいなファイル管理をしてどれが正しいドキュメントかわからなくなりカオスになる
・ソースコードの静的チェックツール(CheckStyleやFindbugsなど)を使わずに、目視と気合でひたすらソースコードレビューする
・Excel表で書かれたテストケースを睨みながら、手でケースに沿ったオペレーションをしてデバッガと睨めっこしたり画面キャプチャをひたすら取りまくる
・せっかくたくさんテストを行ったのに、そのあと本番環境向けに設定ファイルを手動で修正し、内容を間違えたままテストもせずにパッケージング・デプロイしてトラブルを起こす


本当は笑い事ではないのですが、上記のような状況は、開発者の方であれば一度は経験があったり、ニヤッとしてしまうのではないでしょうか。私も例に漏れずそうした経験をしてきたからか、エンジニアの開発環境の改善や開発サイクル全般の作業の自動化などに興味があります。

■背景


一般に、開発作業において
・最新のソースコードをチェックアウト
・ソースコードの静的解析
・成果物のビルド
・テストの実行と結果検証
・ステージング環境、本番環境へのデプロイ

上記のような作業は、何度も繰り返される作業です。特に弊社が行なっているようなWebサービスの開発においては、リリースされて終わりではなく、バグ修正や新機能の追加といったことを継続していくことでサービスが成長していき、そこに価値が生まれていきます。上記のような何度も繰り返す単調な作業を、毎回手で行なっていると、人がやることですからミスも発生しますし、無駄なコストがかかります。何より、どれも結構気を使う作業というか、エンジニアにとってコストがかかる割に、とても精神が擦り減る作業だと思います。

開発作業の本質である価値を生み出す活動に注力するのはもちろんのこと、そういった活動に注力できる環境を作るために、上記のような、どちらかと言えば開発作業の本質ではない所を、コンピュータを使って自動化することでミスを無くし、単調な作業からエンジニアを解放することが重要です。こういった考え方は「継続的インテグレーション(Continuous Integration:CI)」として2000年頃に Martin Fowler によってXPのプラクティスの一つとして広められ、すでに一般にも定着してきていると思います。※1 ※2

CIツールで有名なJenkinsの開発者である川口耕介さんも、「エンジニアが働くのではなく、コンピュータを働かせるのがエンジニアの仕事」と仰っています。(http://www.slideshare.net/kohsuke/hudson-jjug-ccc-presentation

弊社でも部分的ではありますが、継続的インテグレーションを導入しているプロジェクトはあります。
一方で、継続的インテグレーションを導入したいと思っていてもなかなか上手くいかない、心理的な障壁が高い、といった声もよく聞きます。そこで、継続的インテグレーションを導入する心理的なハードルを少しでも下げられないか、どうにか楽に自動化の仕組みを作れないか、そういったことを解決する一つの選択肢として私が注目している技術が、GroovyとGradle、Spockです。
Groovyとそのエコシステムを形成するツール・ライブラリ群はG*と呼ばれています。

■Groovy


社内では様々な言語が使われていますが、サーバサイドの標準としてはJavaが多く使われています。JVMの上で動作する言語としては、他にも最近流行のScalaや、JRubyなどがありますが、その中でも、Groovyが持つJavaとの高い親和性、Javaと比べてより直感的にかつ簡潔に記述できることや、スクリプトとして簡単に実行できる点、容易に既存Javaライブラリを流用できる点などは、Groovyを導入するにあたって社内のJava資産に対しても、違和感なく取り入れることができると考えています。JavaコードがほぼそのままGroovyコードとしても動作する点など、Java開発者が無理なく移行できる学習曲線のなだらかさも魅力的に感じています。パフォーマンスを求められるようなところをJavaと完全に置き換えるのは難しいと思いますが、テストコードやビルドスクリプトなど、パフォーマンスをそこまで求められないような領域に関しては、有効に使えると思っています。

私が特に気に入っているGroovyの特徴として、

・クロージャとGDKメソッドを用いた直感的なコレクション操作
・情報が一目瞭然で確認しやすいPower Assertの出力

def list = [1,2,3] + [4,5]
assert list == [1,2,3,4,5]
list -= [3,5]
assert list == [*[1,2],4]
list *= 2
assert list == [1,2,4,1,2,4]
list.sort()
assert list == [1,1,2,2,4,4]
list.unique()
assert list == [1,2,4]
assert list.sum() == 7
assert list.join("/") == "1/2/4"
assert list.each { print "value:${it * 2}," } // => value:2,value:4,value:8,
def map = list.groupBy { it % 2 }
assert map == [1:[1],0:[2,3]] // 本来は[1:[1],0:[2,4]]


出力例
Caught: Assertion failed: 

assert map == [1:[1],0:[2,3]]
| |
| false
[1:[1], 0:[2, 4]]

Assertion failed:

assert map == [1:[1],0:[2,3]]
| |
| false
[1:[1], 0:[2, 4]]

at List.run(List.groovy:15)


・Grapeを使った手軽なJavaライブラリの利用
・MarkupBuilderやCliBuilder、JsonBuilderなどの各種Builder
・privateプロパティに直接アクセスできるので、DIやモックでいちいち差し替えなくても直接テストできる

// Grapeでスクリプト実行時に直接MavenリポジトリからJacksonダウンロードして使う
@Grab(group='org.codehaus.jackson', module='jackson-mapper-asl', version='1.9.2')
import java.text.SimpleDateFormat
import org.codehaus.jackson.map.ObjectMapper
import groovy.json.*

/*
* プライベートなフィールドにプロパティアクセス
*/
class Data {
private int id
private String message
private String createdAt
}

def map = ["FirstName":"Yusuke","LastName":"Ikeda","Age":29]
def list = ["father","mother","brother"]

def sdf = new SimpleDateFormat("yyyy/MM/dd-hh:mm:ss")
def data1 = new Data()
data1.id = 1
data1.message = "hogehoge"
data1.createdAt = sdf.format(new Date())

def data2 = new Data()
data2.id = 2
data2.message = "fugafuga"
data2.createdAt = sdf.format(new Date())

def dataList = [data1,data2]

def values = [*:map,"family":list,"Data":dataList]

/*
* JacksonでJSONにシリアライズ
*/
def mapper = new ObjectMapper()
def string = mapper.writeValueAsString(values)
println string

/*
* GroovyのJsonSlurperを使ってJSONをパース
*/
def slurper = new JsonSlurper()
def root = slurper.parseText(string)
println root.toString()

/*
* GroovyのJsonBuilderを使ってJSONを作成
*/
def json = new JsonBuilder()
json (
[1,2,3],
[
key1:'value1',
key2:'value2',
key3:'value3',
key4:''
]
)
println json.toString()


実行結果(※整形してあります)

// Jacksonの出力
{"FirstName":"Yusuke",
"LastName":"Ikeda",
"Age":29,
"family":["father","mother","brother"],
"Data":[{"id":1,
"message":"hogehoge",
"createdAt":"2012/01/19-07:05:38"},
{"id":2,
"message":"fugafuga",
"createdAt":"2012/01/19-07:05:38"}]}

// GroovyでJbrotherをパース
[Data:[[message:hogehoge,
id:1,
createdAt:2012/01/19-07:05:38],
[message:fugafuga,
id:2,
createdAt:2012/01/19-07:05:38]],
Age:29,
family:[father, mother, brother],
FirstName:Yusuke,
LastName:Ikeda]

// JsonBuilderでJSON作成
[[1,2,3],
{"key1":"value1",
"key2":"value2",
"key3":"value3",
"key4":""}]


などがあります。
上記のようなGroovyの特徴を活かせば、Javaでテストコードを書くのに比べてより気軽にテストコードを書けるのではと思います。


■Gradle


Gradleは、AntやMaven、最近だとScalaのsbtなどの類のビルドツールです。
ビルドスクリプトをAntやMavenのようにXMLで書くのではなく、GroovyによるDSLで書くことができます。
例えば環境によって設定ファイルを読み分けたい場合、Antのbuild.xmlだと冗長な記述になりがちですし、逆にMavenで混みいったことをやろうとすると、pom.xmlに親子関係を持たせたり、結局Maven Antrun pluginを使って処理を切り分けたりしてイマイチ小回りが利かなかったり、などといったケースは多々あると思いますが、その点Gradleでは、生のGroovyでそのまま書くことができ、さらにMavenと同等の依存性解決をしながら、Antタスクを呼び出すこともできるため、柔軟にビルドスクリプトを書くことができます。
また、Mavenのディレクトリ構成を踏襲しているので、Mavenで構築してきたプロジェクト構成に影響を与えずに導入することができます。
以下に、gradleのビルドスクリプトファイルのサンプルを示します。



usePlugin 'groovy'

repositories {
mavenCentral()
}
dependencies {
groovy 'org.codehaus.groovy:groovy-all:1.7.8'
testCompile 'junit:junit:4.9'
}


dependenciesに記述したライブラリは、Ivyを使って自動的に取得されます。この設定ファイルを、gradleコマンドに渡すと、groovyプロジェクトをJunitを使ってビルド、テストまでを行います。


■Spock


Spockは、Groovyで実装されたBDD(Behavior Driven Development)フレームワークです。今回の紹介の中では主にJUnitの代替となる位置づけで使うイメージです。
Spockに関しては、実際のテストコードを見れば特徴は一目瞭然です。
以下にSpockの公式サイトのサンプルコードを示します。()

@Grab('org.spockframework:spock-core:0.5-groovy-1.8')
@GrabExclude('org.codehaus.groovy:groovy-all')
import spock.lang.*

class HelloSpec extends Specification {
def "length of Spock's and his friends' names"() {
expect:
name.size() == length

where:
name | length
"Spock" | 5
"Kirk" | 4
"Scotty" | 6
}
}


これを見るだけで何を検証しようとしているのかほぼわかるのではないでしょうか。
また、1つのケースに対して色々な値のパターンのテストを簡単に書けるので、テストコードを書く心理的負担を下げてくれると思います。
メソッド名(テスト名)も日本語で書こうと思えば書けるので、コードそのものがそのままテスト仕様書にもなりえます。


●足し算アプリ作ってSpockでテストしてGradleでビルドしてJenkinsでCIする
最後に、簡単な足し算のアプリに対して、Spockでテストコードを書き、Gradleでビルド、テストを実行するビルドスクリプトをJenkinsのジョブから実行する一連の流れを試してみたので以下に示します。

●ディレクトリ構成
engineer-blog/
├── build
│ ├── classes
│ │ ├── main
│ │ │ └── AddSample.class
│ │ └── test
│ │ └── AddSampleSpec.class
│ ├── dependency-cache
│ ├── libs // ビルド成果物
│ │ └── engineer-blog.jar
│ ├── reports // テスト結果ディレクトリ
│ │ └── tests
│ │ ├── AddSampleSpec.html
│ │ ├── base-style.css
│ │ ├── css3-pie-1.0beta3.htc
│ │ ├── default-package.html
│ │ ├── index.html
│ │ ├── report.js
│ │ └── style.css
│ ├── test-results
│ │ └── TEST-AddSampleSpec.xml
│ └── tmp
│ ├── jar
│ │ └── MANIFEST.MF
│ └── test
├── build.gradle
├── gradle
│ └── wrapper // JenkinsからGradleを実行するためのラッパー
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
  ├── main
  │ ├── groovy
  │ └── java
  │ └── AddSample.java // Javaのプロダクトコード
  └── test
  ├── groovy
  │ └── AddSampleSpec.groovy // Spockのテストコード
  └── java


●build.gradle
apply plugin: 'groovy'

repositories {
mavenCentral()
}

dependencies {
groovy 'org.codehaus.groovy:groovy-all:1.8.5'
testCompile 'org.spockframework:spock-core:0.5-groovy-1.8'
}


●AddSampleSpec.groovy
import spock.lang.*

class AddSampleSpec extends Specification {
def "パラメータを受け取り、足し算する"() {
setup:
def obj = new AddSample()

expect:
obj.add(a, b) == result

where:
a | b | result
1 | 1 | 2
2 | 2 | 4
0 | 1 | 1
}
}


●プロダクトコード
class AddSample {

public int add(int a, int b) {
return a - b; // わざとテストを落とす
}
}



●reportディレクトリの中にあるテスト結果
サイバーエージェント 公式エンジニアブログ-spock_result

●Jenkinsの設定
1. Subversionリポジトリから最新のソースコードをチェックアウト
2. ビルドスクリプトとして ./gradlew -q test --info と指定
 ・gradlew はJenkinsサーバにGradleがインストールされていなくても実行できるようにするためのラッパー
 ・test タスクを実行している。 clean build とすればビルドまで実行する

●結果
サイバーエージェント 公式エンジニアブログ-jenkins_setting
サイバーエージェント 公式エンジニアブログ-jenkins_result


■まとめ


今回は、ビルド・テストの自動化を楽にする技術として、Groovy及びGradle、Spockをご紹介しました。
Groovyとそれを取り巻くツールは、決してメジャーではありませんが、Java開発においては既存の資産を流用しながらも、より柔軟に使い勝手を向上してくれる、かゆいところに手が届く技術だと思います。
JenkinsのScriptConsoleにGroovyが使えたり、最近社内でも沸々と人気が出てきているPlayFrameworkでも使える言語なので、Java開発を行う方にとって使えて損はない言語です。これから利用も増えてくると思っていますし、私自身としても今後の業務の中で積極的に取り入れていきたいと思っています。

現在、私とDevLoveさんの有志の方々で、近いうちに初心者向けのゆる~いGroovyハンズオンのような集まりも企画しています。ご興味がある方は@yukungまでぜひお声がけください!お付き合いいただきありがとうございました。


[脚注]
※1 参考文献:http://ec.nikkeibp.co.jp/item/books/P83950.html
※2 さらに一歩進んだ考え方として、継続的デリバリという概念も出てきています。