cuke4duke を使い Cucumber を Java で使う

先日のTDDBC 札幌 2.1 でCucumberの評判が良く、ATDD(受入テスト駆動開発)へ興味を持った人も多かったため、JavaでのCucumberの利用方法を調べてみました。以前調べた時は見つけられなかったのですが、今は cuke4duke というツールを使うことで、JavaやGroovyを使ってJavaアプリケーションでも簡単に Cucumber を利用できるようです。ちなみに、 cuke は cucumberの愛称だそうで、dukeJavaのアレですね。
ちなみに2011/09/24現在、cuke4dukeの開発は止まっており、後継となるcucumber-jvmの開発が行われています。ですが、現時点ではcucumber-jvmを動かすことは出来ませんでした。リリースもいつ頃かちょっと不明です。とはいえ、「Cucumber for the JVM (successor of Cuke4Duke)」と書いてあるので、まずはCuke4Dukeを知っておけば移行もスムーズにできると思います。

準備

cuke4dukeは非常によくできたツールなのですが、ドキュメントが壊滅的にありませんでした...。そして、致命的なのはRubyの文化もJavaの文化も解らないと環境構築だけで挫折しそうという所です。この辺は検索しても、あまりヒットしないので、少し細かく書いておきます。
まず、cuke4dukeの仕組みを簡単に説明すると、「JRuby上でCucumberを実行し、JavaやGroovyで記述されたステップファイルを実行」します。実行はコマンドラインMavenで実行できます。また、JRubyはRVM上に構築しておくと便利です。
というわけで、以下のセットアップを必要とします。
JDK + Maven2

  • RVM + JRuby
  • Cucumber(cuke4duke)

尚、MacSnow Leopard)での構築方法について記述しますが、RVMあたり以外はWindowsでも同じようにできると思います。

Mavenコマンドラインか?

Mavenによる実行はJenkinsなどCIを使う場合は非常に便利です。実行時間はコマンドラインからよりも時間がかかるのですが、統合されたビルドツールに含められるというのは非常に大きなアドバンテージです。しかし、Mavenで実行する場合、他のビルドプロセスも動いたり、都度JVMが起動し、さらにJRubyの環境が読み込まれたりとオーバーヘッドが高く、サクサク開発するために使うには問題が残ります。Antを使った場合でも同様でしょう。
コマンドラインからの実行では、RubyでCucumberを使うようにJRubyで利用できる事が最大のアドバンテージです。@wip(work in progress)タグを利用すれば簡単に作業中のシナリオのみを実行できるなどメリットは多くあります。しかし、IDEから利用しにくい事、JRubyの起動はやはり遅い事がデメリットです。尚、JRubyの起動が遅い問題については、Groovyservを使う事で劇的に改善させられると思い調査もしてみましたが、コマンドを上手く認識していないのかまだ成功していません。
他の実行手段として、後継のcucumber-jvmではJUnitのテストケースとして実行する方法が提供されるようです。これは、Eclipse上から簡単に実行できるということです。まだ詳細はわかりませんが、気になる機能かと思います。
まとめると、CIで利用するにはMaven。それ以外であればコマンドラインというのが現状の実行方法かと思います。

JDK + Maven2

JDKはインストールされていないならインストーラ等を使ってインストールしてください。Mavenはまだ2系を使っておいた方が無難です*1Mavenのサイトより、2.2.1をダウンロードし、適当なディレクトリに解凍したら、パスを通すだけで準備は完了します。以上のセットアップが終わったならば、mvn コマンドを実行して確認します。

$ mvn --version
Apache Maven 2.2.1 (r801777; 2009-08-07 04:16:01+0900)
Java version: 1.6.0_26
Java home: /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
Default locale: en_US, platform encoding: SJIS
OS name: "mac os x" version: "10.6.8" arch: "x86_64" Family: "mac"
MacBookPro:groovy shuji$

RVM + JRuby

RVMはRubyの実行環境を複数インストールして切り替えて利用できるようにするためのツールです。MacではデフォルトでRuby 1.8.7がインストールされていますが、それを残したままJRubyを利用できるようにします。また、Cucumberなどの追加するライブラリもRVMのディレクトリ(ホーム以下)に入るため環境を汚染しないというメリットもあります。
RVMのインストールは、git からソースを取得してインストールする方法とgemを使ってインストールする方法があるようですが、gemによるインストールは結局動きませんでした。多のサイトでもgitからインストールすることを推奨しているようです。gitがインストールされていない場合はググってください。

mkdir -p ~/.rvm/src
cd ~/.rvm/src
git clone git://github.com/wayneeseguin/rvm.git
cd rvm
./install

rvmは~/.rvm以下にインストールされるので、以下のスクリプトを実行してパスを通します(~/.bashrcに追加)。

-s "$HOME/.rvm/scripts/rvm" && source "$HOME/.rvm/scripts/rvm"

続けてJRuby-1.6.4をrvmのコマンドを使いインストールします。

rvm install jruby-1.6.4

JRubyは~/rvm以下にインストールされます。結構な容量がありますので注意してください。今後は以下のコマンドを使うことで、利用するRubyJRubyに切り替えることができます。

$ rvm use jruby
$ ruby --version
jruby 1.6.4 (ruby-1.8.7-p330) (2011-08-23 17ea768) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_26) [darwin-x86_64-java]
Mac

cuke4dukeのインストール

cuke4dukeもgitから取得してインストールしますが、jarファイルを作るためにmavenコマンドが必要です。Mavenによるビルドは利用したことのないプラグインや依存ライブラリをダウンロードして行いますので、時間がかかるので注意してください。

git clone https://github.com/cucumber/cuke4duke
cd cuke4duke
mvn package

以下、利用しているRuby環境(RVM上のJRuby)のgemに追加されます。

gem install bundler
bundle install
rake install

以上でcuke4dukeのインストールは完了です。

サンプルの実行

exampleディレクトリにサンプルプロジェクトが幾つか用意されているので、まずはこれらを実行してみます。

javaプロジェクト

example/javaは、シンプルなJavaプロジェクトです。コンパイルされていませんので、mvnコマンドで最初にコンパイルします。また、プロジェクトで利用しているライブラリをプロジェクト配下にコピー*2する必要があります*3

mvn package dependency:copy-dependencies

コマンドラインから実行するには、依存ライブラリとクラスファイルなどのパスを指定します。

cuke4duke --jars target/dependency --require target/test-classes

    • requireには、フィーチャの実行に必要なファイルのあるディレクトリを指定し、--jarsには実行時に必要なライブラリのあるディレクトリを指定します。ただ、依存関係の解決などに不具合が多いようでコマンドラインからの実行はうまくいかない場合が多いです(特にGroovyなどを使う場合)。

Mavenで実行するにはプラグインが自動的に依存ライブラリなどは解決するので、以下のコマンドで簡単に実行できます。

mvn -Dcucumber.installGems=true integration-test

Javaでステップを記述する場合は、Javaのクラスとして記述し、--requireでクラスファイルがあるディレクトリを指定しなくてはなりません。他の言語でステップファイルを記述する場合は、featuresの下にstep_definitionsを作りスクリプトファイルを配置します(Groovyなどで対応)。

package simple;

import cuke4duke.annotation.I18n.EN.Given;
import cuke4duke.annotation.I18n.EN.Then;

import static org.junit.Assert.assertTrue;

public class CalledSteps {
    private boolean magic;

    @Given("^it is (.*)$")
    public void itIs(String what) {
        if (what.equals("magic")) {
            magic = true;
        }
    }

    @Then("^magic should happen$")
    public void magicShouldHappen() {
        assertTrue(magic);
    }
}
>>

*1:Pluginの対応や情報が少ないため

*2:Mavenプロジェクトの場合、ローカルリポジトリにライブラリが保存され、コンパイル・実行時にパスを通す

*3:この辺り面倒です…