opamp_sando's blog

✝クソザコが割りと適当なことを書くためにある備忘録です✝

jewelerでCucumberを使ってみる。

jewelerでcucumberを使ってテストコードを書く。
とりあえず今回作ったテストプロジェクトをgithubにおいた。
https://github.com/opamp/testProject

とりあえずgemでcucumber,rspec,jewelerなどをインストールする。

$ gem install cucumber rspec jeweler

とする。
以前の記事でjewelerの基本的な開発手順は書いたのでjewelerについてはそっちを参照。

とりあえずインストールができたら適当なディレクトリに移動して早速プロジェクトを作る。

$ jeweler --cucumber --rspec testProject

のようにすると cucumberとRSpecを使った "testProject" という名前のプロジェクトを作ってくれる。また、--reposを追加することで自動的にgithubやRubygemsにrake release時にアップデートしてくれるようになる。
今回は--reposはなくてもおk。

cucumberはどうみてもプレインテキストなファイルを書いてそれでテストするというツール。
とりあえずjewelerでプロジェクトを作ったらまずはRakefileの中の以下の2行を適切に設定しよう。

 gem.summary = %Q{test project}
 gem.description = %Q{This project was made for practice.}

プロジェクトについての説明を記述すればいい。

で、ここまでできたら

$ rake version:write

としてVersionをつける

$ git add .
$ git commit -m "my first commit."

としてコミットしておく。

じゃあ早速Cucumberを書いてみる。 (説明がよくわからなかったらリポジトリの方も参照するといいかもしれない)
あと、CucumberのgithubのExamplesも役に立つ。(というか自分はひたすらこれを見ながら書いた)
https://github.com/cucumber/cucumber/tree/master/examples/i18n


とりあえずプロジェクトディレクトリの中にfeaturesというディレクトリがあると思う。Cucumberのテストコードは全部ここに入っている。(jeweler --cucumberで作ると自動で生成されているはず)

まず簡単にファイルの説明。 featuresに入ると

$ cd features
$ ls
step_definitions support testProject.feature

という感じにファイルがある。 testProjectという部分はこのプロジェクトの名前になっている。 この.featureファイルがテストを記述するファイルだ。vimとかで開いてみるとどうみてもプレインテキストになっている。

で、supportディレクトリの中にenv.rbがあると思う。こいつはよくわからなかったがとりあえず無視していい・・・ たぶんrequire系の処理を書いてるのだと思う。
(ちなみにこのsupport/env.rbの代わりにこれから書くstep_definitions/testProject_steps.rbの頭にenv.rbの中身を書いても動作すると思う。まあせっかくjewelerが作ってくれてるのでそんなことする必要はないけど。)

で、最後にstep_definitions以下のファイルに.featureのプレインテキストを判定するRubyコードを記述する(面倒だと思うけど実は結構簡単に面白く作れた)
とりあえずこれらのファイルはCucumberのサンプルにもあるように日本語での記述も可能だが今回はとりあえずヘタな英語で記述してみた。

以下がstep_definitions/testProject_steps.rbの中身

#-*- encoding:utf-8 -*-
#begin require 'rspec/expectations'; rescue LoadError; require 'spec/expectations'; end 
#require 'cucumber/formatter/unicode'
#$:.unshift(File.dirname(__FILE__) + '/../../lib')
#require 'testProject'

Before do
	@greetingObj = Hoge.new()
end

After do
end

Given "My name is $n" do |n|
	@greetingObj.name = n
end

When /^He say (\w+)$/ do |s|
	@result = @greetingObj.hello()
end

Then /^He said was (.+)$/ do |r|
	@result.should == r 
end

で次のがtestProject.featureファイル

Feature: say Hello your name
	This class will be able to say 'hello' to your name?
	I'll check this action.

  Scenario Outline: Say Hello to your name
    Given My name is <myname>
    When He say <s>
    Then He said was <r>

  Examples:
	| myname | s     | r          |
	| opamp  | hello | Hello opamp|
	| sando  | hello | Hello sando|

こんな感じ。

まずstep_definitions/testProject_steps.rbを見てみる。Beforeという処理が最初にあるがこれはGivenが実行される前に行われる処理。RSpecのbeforeと同じような感じになっている。
今回はここでクラスのインスタンス化を行っている。これでGivenやWhenからここで初期化しておいたこのクラスのObjectである@greetingObjを使うことができる。

Afterも書けるが今回は書くことがないので空白にしておいた

Givenを見る前に今回作ったHogeクラス(名前が適当w)の動作について簡単に書いてみる。
Hogeはattr_writerされたnameというインスタンス変数を持っている。ここに名前を入れることができる。
で、Hogeクラスはhelloというメソッドを持っていてこれを実行すると "Hello name" と先ほどの変数nameと組み合わせたHello ...という文字列を返す。今回のテストはこのhelloがちゃんと正しい文字列を返すかをテストする。

じゃ、Givenを見ていく。Givenには前提条件を書く。今回の場合自分の名前を設定するようにした。
「私の名前は○○だ」というのを前提の条件にしてみた。 なので

 Given "My name is $n" do |n|
       @greetingObj.name = n
end

としてGivenを書いた。 featureファイルをチラッと見ればわかるがfeatureのGiven部分に書かれたテキストはこいつのマッチする。
でnaemの部分は可変なので$nとしておく。 ここで与えられた名前についてブロックを実行する。
@greetingObj.name = n としてオブジェクトに名前を教えてあげている。

こんな具合でWhen,Thenも書いていく。Whenには実際の処理。今回はhelloメソッドが正しく動作するかを知りたいので Whenでhelloメソッドを実行するような処理を書く。

でThenではWhenで実行した内容が正しいか...helloメソッドがちゃんと期待した文字列を返してくれてるかをチェックするように書いている。
He said was (.+) という風にしている。 wasのあとに何が入るかはfeatureファイルで記述する。
で、ここに入れられた文字列がWhenの結果の値と等しいかをチェックしている
@result.should == r
ここは .shouldでやらないといけないよ。


ということで次にfeatureファイルを見ていく。3つに中身が分けられることがわかる。 Feature: と Scenario Outline: と Examples: の3つ。 ちなみにExampleを書かない書き方とか他にもいくつか書き方があるようだ。
Featureにはこのテストの説明のようなことを書くようになってるのかな。とりあえずここは何をテストするかがわかるように書けばおk。

ということで次にScenario Outline:を見ていく。Scenario: という書き方もあるがその辺は最後におまけみたいな感じで書く。
とりあえずScenario Outlineの中を見るとstep_definitions/testProject_steps.rbで書いたGivenとWhenとThenの3つに分けられるのがわかる。
それぞれstep_definitions/testProject_steps.rbで記述したこととマッチするような書き方になっている。
などはExamples:の値でそれぞれ置き換えられて実行されるようになっている。なので下のExamplesのそれぞれの値が入ってテストされる。

Examples:の書き方は例の通りに書いてやればいい。特に難しいことはない | で区切っていくだけ。
これを下にずらーっと書いていけばいろんなパターンでテストできることになる。今回はmynameがopampのパターンとsandoのパターンの2パターンでテストしている。


とりあえずこれでcucumberは書き終わった。RSpecの方も適当に書いたことにしよう。
じゃあ早速テストを実行してみる。

プロジェクトのルート(Rakefileとかあるところ)に戻る。

$ rake -T

とすると

rake/rdoctask is deprecated. Use rdoc/task instead (in RDoc 2.4.2+)
rake build # Build gem into pkg/
rake clobber_rdoc # Remove rdoc products
rake console[script] # Start IRB with all runtime dependencies loaded
rake features # Run Cucumber features
rake gemcutter:release # Release gem to Gemcutter
rake gemspec # Generate and validate gemspec
rake gemspec:debug # Display the gemspec for debugging purposes, as jeweler knows it (not from the filesystem)
rake gemspec:generate # Regenerate the gemspec on the filesystem
rake gemspec:release # Regenerate and validate gemspec, and then commits and pushes to git
rake gemspec:validate # Validates the gemspec on the filesystem
rake git:release # Tag and push release to git.
rake install # Build and install gem using `gem install`
rake rcov # Run RSpec code examples
rake rdoc # Build the rdoc HTML Files
rake release # Release gem
rake rerdoc # Force a rebuild of the RDOC files
rake spec # Run RSpec code examples
rake version # Displays the current version
rake version:bump:major # Bump the major version by 1
rake version:bump:minor # Bump the a minor version by 1
rake version:bump:patch # Bump the patch version by 1
rake version:write # Writes out an explicit version.

となると思う。 cucumberのテストを実行するには

$ rake features

を実行すればよい。(RSpecなら rake spec ね。)

とりあえず早速実行してみる

$ rake features

rake/rdoctask is deprecated.  Use rdoc/task instead (in RDoc 2.4.2+)
/Users/opamp/.rvm/rubies/ruby-1.9.2-p290/bin/ruby -S bundle exec cucumber 
Feature: say Hello your name
  This class will be able to say 'hello' to your name?
  I'll check this action.

  Scenario Outline: Say Hello to your name # features/testProject.feature:5
    Given My name is <myname>              # features/step_definitions/testProject_steps.rb:14
    When He say <s>                        # features/step_definitions/testProject_steps.rb:18
    Then He said was <r>                   # features/step_definitions/testProject_steps.rb:22

    Examples: 
      | myname | s     | r           |
      |Hello opamp
 opamp  | hello | Hello opamp |
      |Hello sando
 sando  | hello | Hello sando |

2 scenarios (2 passed)
6 steps (6 passed)
0m0.006s

となる。若干表示がずれてるきがしないでもないがまあいいや。上のようになれば成功。

じゃあ、今度はExamples:の opampをあえて opaamp としてテストを失敗させてみる。

rake/rdoctask is deprecated.  Use rdoc/task instead (in RDoc 2.4.2+)
/Users/opamp/.rvm/rubies/ruby-1.9.2-p290/bin/ruby -S bundle exec cucumber 
Feature: say Hello your name
  This class will be able to say 'hello' to your name?
  I'll check this action.

  Scenario Outline: Say Hello to your name # features/testProject.feature:5
    Given My name is <myname>              # features/step_definitions/testProject_steps.rb:14
    When He say <s>                        # features/step_definitions/testProject_steps.rb:18
    Then He said was <r>                   # features/step_definitions/testProject_steps.rb:22

    Examples: 
      | myname | s     | r           |
      |Hello opaamp
 opaamp | hello | Hello opamp |
      expected: "Hello opamp",
           got: "Hello opaamp" (using ==) (RSpec::Expectations::ExpectationNotMetError)
      ./features/step_definitions/testProject_steps.rb:23:in `/^He said was (.+)$/'
      features/testProject.feature:8:in `Then He said was <r>'
      |Hello sando
 sando  | hello | Hello sando |

Failing Scenarios:
cucumber features/testProject.feature:5 # Scenario: Say Hello to your name

2 scenarios (1 failed, 1 passed)
6 steps (1 failed, 5 passed)
0m0.007s
rake aborted!
Command failed with status (1): [/Users/opamp/.rvm/rubies/ruby-1.9.2-p290/b...]

Tasks: TOP => features
(See full trace by running task with --trace)

こんな風になる。
(ホントはテスト失敗させるならテストのファイルを書き換えずにクラス本体を書き換えてやったほうがいいかもしれんが)

とりあえず今日はこんな感じ。非常にわかりにくかったと思うけど自分用備忘録としては十分だと思う・・・


ところでfeatureファイルのScenario Outline:のところだけど Scenario: として

Given My name is opamp

と直接記述することも可能。またこうした場合に複数のパターンをテストしたい時はどうするかというと
普通にExamples:では動かなくなるので ExampleをMore Examples:にしてやるといいようだ。(試してないが)
http://d.hatena.ne.jp/moro/20081112/1226486135
ここをちらっと見るとそういう記述をしてるようだった。

あとさっきも貼ったけど
https://github.com/cucumber/cucumber/tree/master/examples/i18n
も参照にするといいかも。日本語での書き方とかの例も載ってる。

Firefox ブラウザ無料ダウンロード