JRubyいろいろ - BDDなんかやってみちゃうぞぉ

BDD(Behavior Driven Development)、振舞駆動開発、人気ですねぇ。「Ruby逆引きレシピ」では、、、ありました、レシピ181。Rspecを使ったBDD。今や、Rubyな世界ではテストっていうとBDDな感じで、rspecとか、そう、あのcucumberが大〜人気。RailsConfですっかりcucumberにかぶれて帰ってきたところなので、今回はcucumberでBDDなんかやってみちゃうぞぉ、と。"Make cukes green!"を目指して、きゅうりをかじってみよう!


その1 きゅうり、持ってこい!Cucumberのインストール

きゅうりが無いとかじるものもないので、まずは定番のインストールから。Cucumber gemが必要なのは言うまでもないので、気になるのは他に必要なものだよね。。。で、必要なのはrspecだけ。ちまたではよく"cucumberはwebratが必要"みたいなとことが書かれているけれど、、、Railsのテストじゃなければ、webratはいらない。まぁ、cucumberはふつーRailsのテストに使うから、webratもインストールしておけばいいんですけどね。

で、JRubyの場合、「JRubyいろいろ - gem, gem, gem (http://d.hatena.ne.jp/yokolet/20100602#1275509300)」のその1に書いたようにrspecは最初から入っているから、cucumberのインストールだけでよろしい、と。つまり、きゅうりを持ってくるのに必要なのはコレだけ。

jruby -S gem install cucumber


その2 こんなきゅうり、あります。Cucumberのサンプル

Cucumberをインストールしたら、jruby-x.y.x/lib/ruby/gems/1.8/gems/cucumber-0.8.3/examplesを眺めてみる。あるわあるわ、きゅうりがたくさん、、、サンプルがたくさん。このきゅうりただものじゃない。ほんとに。Rubyのためのテスティングフレームワークじゃなくて、Rubyで書いた、汎用(いろいろな言語で使える)テスティングフレームワークな感じです。i18nのサンプルについてはその3で試してみるとして、他は、、、えっと、失敗してしまうものとか、ruby2pythonのように、必要なgemがJRuby用にポートされていないとかあるでの、make cukes green!はユーザのために残してあるのかぁ、、、と。(^^;; でも、いろいろあるし、Cucumber Wiki (http://wiki.github.com/aslakhellesoy/cucumber/)にいくと、説明がいろいろあるので、試してみるとおもしろいかも。


その3 きゅうりの日本語レシピは?

そして、cucumberと言えば、第22回 Railsアプリの受け入れテストをCucumberで書こう:Ruby Freaks Lounge|gihyo.jp … 技術評論社にちょっとしたサンプルがあるように、自然言語で振舞を記述するワケですが、その自然言語も英語だけじゃなくて、44ヶ国語(version 0.8.3)に対応! (jruby -S cucumber --i18n help で表示される) なのでした。Ruby Freaks Loungeのcucumberの回では、misoというgemを使って日本語に対応していたけれど、今はもうmisoはいらないみたい。きゅうりにみそ、、、ん、これは、、、もろきゅうだね、しゃれてる、と思ったのですが、ね。今は何も付けずにヘルシーにきゅうりをバリバリと食べられるようになっているみたいだ。

ついでに、cucumberキーワードがどんな風に日本語になっているかというと、、、

$ jruby -S cucumber --i18n ja
      | feature          | "フィーチャ", "機能"                                  |
      | background       | "背景"                                           |
      | scenario         | "シナリオ"                                         |
      | scenario_outline | "シナリオアウトライン", "シナリオテンプレート", "テンプレ", "シナリオテンプレ" |
      | examples         | "例", "サンプル"                                    |
      | given            | "* ", "前提"                                     |
      | when             | "* ", "もし"                                     |
      | then             | "* ", "ならば"                                    |
      | and              | "* ", "かつ"                                     |
      | but              | "* ", "しかし", "但し", "ただし"                       |
      | given (code)     | "前提"                                           |
      | when (code)      | "もし"                                           |
      | then (code)      | "ならば"                                          |
      | and (code)       | "かつ"                                           |
      | but (code)       | "しかし", "但し", "ただし"                             |

のようです。

ということで、jruby-x.y.x/lib/ruby/gems/1.8/gems/cucumber-0.8.3/examples/i18n/jaを眺めてみると、、、ありました!こんなfeature

# language: ja
フィーチャ: 加算
  バカな間違いを避けるために
  数学オンチとして
  2つの数の合計を知りたい

  シナリオテンプレート: 2つの数の加算について
    前提 <値1> を入力
    かつ <値2> を入力
    もし <ボタン> を押した
    ならば <結果> を表示

  例:
    | 値1 | 値2 | ボタン | 結果 |
    | 20  | 30  | add    | 50   |
    | 2   | 5   | add    | 7    |
    | 0   | 40  | add    | 40   |

に、こんなsteps

# encoding: UTF-8
Before do
  @calc = Calculator.new
end

After do
end

前提 "$n を入力" do |n|
  @calc.push n.to_i
end

もし /(\w+) を押した/ do |op|
  @result = @calc.send op
end

ならば /(.*) を表示/ do |result|
  @result.should == result.to_f
end

おぉ、これはさっそく試してみよう、、、んー、

$ jruby -S rake cucumber
(in /Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/examples/i18n/ja)
/Users/yoko/Tools/jruby-1.5.1/bin/jruby -I "/Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/lib:lib" "/Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/bin/cucumber" --format pretty
load error: /Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/examples/i18n/ja/features/step_definitons/calculator_steps -- org.jcodings.exception.EncodingException: invalid code point value (LoadError)
/Users/yoko/Tools/jruby-1.5.1/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
/Users/yoko/Tools/jruby-1.5.1/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
/Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/bin/../lib/cucumber/rb_support/rb_language.rb:141:in `load_code_file'
/Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/bin/../lib/cucumber/step_mother.rb:84:in `load_code_file'
/Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/bin/../lib/cucumber/step_mother.rb:76:in `load_code_files'
/Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/bin/../lib/cucumber/step_mother.rb:75:in `each'
/Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/bin/../lib/cucumber/step_mother.rb:75:in `load_code_files'
/Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/bin/../lib/cucumber/cli/main.rb:56:in `execute!'
/Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/bin/../lib/cucumber/cli/main.rb:25:in `execute'
/Users/yoko/Tools/jruby-1.5.1/lib/ruby/gems/1.8/gems/cucumber-0.8.3/bin/cucumber:8
rake aborted!
Command failed with status (1): [/Users/yoko/Tools/jruby-1.5.1/bin/jruby -I...]

(See full trace by running task with --trace)

と、例外がでちゃいました。どうやら、ステップの記述に日本語があるとJRubyはだめみたいだ。ステップのところだけ英語版に変えると動いたけれど、結果はすべてpendingになってしまう、と。これはcucumberが悪いわけではなく、CRuby ではちゃんと動いて、こんな感じでした。

# language: ja
フィーチャ: 加算
  バカな間違いを避けるために
  数学オンチとして
  2つの数の合計を知りたい

  シナリオテンプレート: 2つの数の加算について # features/addition.feature:7
    前提<値1> を入力            # features/step_definitons/calculator_steps.rb:9
    かつ<値2> を入力            # features/step_definitons/calculator_steps.rb:9
    もし<ボタン> を押した          # features/step_definitons/calculator_steps.rb:13
    ならば<結果> を表示           # features/step_definitons/calculator_steps.rb:17

    例: 
      | 値1 | 値2 | ボタン | 結果 |
      | 20 | 30 | add | 50 |
      | 2  | 5  | add | 7  |
      | 0  | 40 | add | 40 |

# language: ja
フィーチャ: 除算
  バカな間違いを避けるために
  有理数も計算できること

  シナリオ: ふつうの数値    # features/division.feature:6
    前提3 を入力       # features/step_definitons/calculator_steps.rb:9
    かつ2 を入力       # features/step_definitons/calculator_steps.rb:9
    もしdivide を押した # features/step_definitons/calculator_steps.rb:13
    ならば1.5 を表示    # features/step_definitons/calculator_steps.rb:17

4 scenarios (4 passed)
16 steps (16 passed)
0m0.014s

JRubyエンコーディング関係はTom Enebo氏などが四苦八苦してなんとかしようと努力中ではありますが、Rubyの1.8と1.9の両方を同時にJavaの上に載せるという複雑さがあって、ところどころでこのような問題が発生しています。Help wanted!


その4 きゅうりのレシピ検証

せっかくなので、Make cukes green!の過程をひとつひとつ試してみようじゃぁありませんか。


ところで、Railsとかいろいろなところのテストを見ると、ディレクトリ名がfeaturesとかなんとか決まっているかのような気がするのですが、これは気のせい。Cucumberはディレクトリは気にしないみたいなので、ちょびっとのサンプルだから全部ひとつのディレクトリの中でやっちゃいましょう。


では、Cucumberのサイト、http://cukes.info/に出ているように、まずはフィーチャーをaddition.featureに記述。

Feature: Addition
  In order to avoid silly mistakes
  As a math idiot
  I want to be told the sum of two numbers

  Scenario: Add two numbers
    Given I have entered 50 into the calculator
    And I have entered 70 into the calculator
    When I press add
    Then the result should be 120 on the screen

もうひとつ、ステップも。これは、とりあえず、Givenのステップひとつだけcalculator_steps.rbに書いてみた。

Given /I have entered (.*) into the calculator/ do |n|
  @calculator = Calculator.new
  @calculator.push(n.to_i)
end

で、jruby -S cucumber addition.featureとタイプすると、、、

というように、予想どおりテストが失敗。ということで、Calculatorクラスを書いてみる。Javaで。Javaのクラスを、ですよ。だってJRubyなんだから。同じディレクトリにこんなCalculator.javaを書いて、

import java.util.ArrayList;
import java.util.List;

public class Calculator {
  private List values = new ArrayList();
  private int index = 0;

  public void push(int v) {
    values.add(index++, v);
    if (index > 2) index = 0;
  }
}

javac Calculator.javaで、Calculator.classを作る、と。Javaのクラスを使っているのでステップ記述のCalculatorはJava::Calculatorに修正、、、

Given /I have entered (.*) into the calculator/ do |n|
  @calculator = Java::Calculator.new
  @calculator.push(n.to_i)
end

ができたら、再びjruby -S cucumber addition.featureを実行すると、、

おぉ、どうやら、pushはできたらしい。あとはpendingになっているaddとresultをなんとかすればよし。で、こんどのステップはこんな感じに。毎ステップでCalculatorのインスタンスを作っていると、足し算の結果を出せないので、Beforeのところで、インスタンス作成。

Before do
  @calculator = Java::Calculator.new
end

Given /I have entered (.*) into the calculator/ do |n|
  @calculator.push(n.to_i)
end

When /I press (\w+)/ do |op|
  @calculator.send op
end

Then /the result should be (.*) on the screen/ do |result|
  result == @calculator.result
end

Javaのクラスはこんなふうに修正。

import java.util.ArrayList;
import java.util.List;

public class Calculator {
  private List values = new ArrayList();
  private int index = 0;

  public void push(int v) {
    values.add(index++, v);
    if (index > 2) index = 0;
  }

  public void add() {
    values.add(2, values.get(0) + values.get(1));
  }

  public int result() {
    return values.get(2);
  }
}

そして、javac Calculator.javaRubyを書き慣れてくると、IDEを使い慣れていると、Javaのプログラムををコンパイルするのを忘れるのですよ。修正したのに変わっていないじゃないか!と。Javaのコードはコンパイルが必要。忘れないようにしよう>自分。
そして、またしても jruby -S cucumber addition.featureを実行すると、、、

Yeees! I made cukes green!


その5 もっとJavaなきゅうりを

ていう感じで、JavaのクラスをCucumberでBDD開発できそう。。。”でもさぁ、JavaだとMavenとか使いたいんだよねぇ”って?あります、そんなJavaなきゅうりが。その名もcuke4duke(http://github.com/aslakhellesoy/cuke4duke)。JRubyがあるからこそ、のこのテスティングフレームワーク。cuke4dukeを使うと、ステップの記述もJavaのコードで書くみたい。たとえば、サンンプルのひとつ、CalledSteps.java

ackage 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);
    }
}

アノテーションですね。中ではPicoContainerなんか使っている様子。サンプルの中にはGuiceとか(!)、Scale、Clojureなんかいろいろあって、おもしろそうです。cuke4dukeについてはまた今度の機会に。いろいろ試してみようと思います。


ということで、BDDなんかやってみちゃった、JRubyいろいろでした。


余談ですが、アメリカのスーパーでは日本のあのいぼいぼがついているきゅうりは売っていません。日本食スーパーに行けばありますが。アメリカもののきゅうりで一番に日本のきゅうりに近いのはbaby cucumberと呼ばれているきゅうりで、ピクルスにするきゅうり。Cucumberっていうきゅうりはどちらかというとズッキーニ系。ちょぉっと”きゅうり”じゃないかなぁ。もっと、ウリな感じ。ズッキーニは輪切りになってサラダのロメインレタスの上にのってきたりする。日本食レストランだと、ズッキーニは天ぷらになって出てくる。これはけっこういける。意外とおいしいです、ズッキーニ天ぷら。