{{toc}} !結果 CucumberとRSpecを用いてBDDでmy_helpのテスト開発を進めて行きました. // アプリの振る舞いを説明する // なぜ自分が作ったかを納得してもらえないから. // 自分の作業の目的の意義を説明するために,全体の概要を説明することは妥当 // 先行研究->先行開発,ソフト開発==研究,why!! ここでは,焦点を合わせたmy_helpの中での一つの振る舞いである 「todoの更新」を例として詳しく見て行きます. !!todoの更新マニュアル 最初に,todoを更新するときの手順を示します. #my_todo --editを入力して~/.my_help/my_todo.ymlを開く #editorでtodoを書き込む(今週やることならweeklyというitemを作ってそこに書き込む) #保存して~/.my_help/my_todo.ymlを閉じる #my_todoと打ち込んで更新されていたら完成 #my_todo --store [item]を入力してitemのバックアップをとる この振る舞いがきちんとできているのかを実際にテスト構築して行きます. !!Cucumber 以下はtodoの更新を行うときのfeatureです. まず,適当なディレクトリにfeaturesというディレクトリを作成します. 次に先ほど作成した,featuresディレクトリにmy_todo.featureを作成します. <<< tcsh # language: ja 機能: todoの更新を行う todoは更新していくものであり,新しく書いたり終わったものを 消したいのでバックアップをとって,過去のtodoを残しておく シナリオ: コマンドを入力してtodoを更新していく 前提 todoを編集したい もし "my_todo --edit"と入力する ならば editが開かれる かつ 自分のtodoを書き込む シナリオ: コマンドを入力してバックアップをとる 前提 todoの編集が終わった もし "my_todo --store [item]"と入力する ならば itemのバックアップを取る >>> 機能とは,このシステムの機能のことを記述します.ここでは,todoを更新するシステムですので,「todoの更新を行う」です. 機能の下には,機能の補足説明を記述します.機能の補足説明では,ルールがないので自分がわかりやすいように,記述するのが常識です. シナリオは,その名の通りtodoを更新する時のユーザの行動やシステム振る舞いを前提,もし,ならば,かつ,しかしに分類して記述します. シナリオは,一つの機能に対して複数書くことが可能です.ここでは、「todoを更新する」と「バックアップをとる」という二つの シナリオをたてています. ここまでfeatureが記述できたら,次はcucumberコマンドを実行してみます. コマンドは以下の通りです.  /Users/nasubi/nasu% cucumber features/my_todo.feature featuresディレクトリにあるmy_todo.featureファイルをcucumberで実行するという意味です. 実行すると以下のようになります. //# language: ja //機能: todoの更新を行う //todoは更新していくものであり,新しく書いたり終わったものを消したいのでバックアップをとって,過去のtodoを残しておく // シナリオ: コマンドを入力してtodoを更新していく # features/my_todo.feature:6 // 前提todoを編集したい # features/my_todo.feature:7 // もし"my_todo --edit"と入力する # features/my_todo.feature:8 // ならばeditが開かれる # features/my_todo.feature:9 // かつ自分のtodoを書き込む # features/my_todo.feature:10 // シナリオ: コマンドを入力してバックアップをとる # features/my_todo.feature:12 // 前提todoの編集が終わった # features/my_todo.feature:13 // もし"my_todo --store [item]"と入力する # features/my_todo.feature:14 // ならばitemのバックアップを取る # features/my_todo.feature:15 <<< tcsh <省略> 2 scenarios (2 undefined) 7 steps (7 undefined) 0m0.080s You can implement step definitions for undefined steps with these snippets: 前提(/^todoを編集したい$/) do pending # Write code here that turns the phrase above into concrete actions end もし(/^"([^"]*)"と入力する$/) do |arg1| pending # Write code here that turns the phrase above into concrete actions end ならば(/^editが開かれる$/) do pending # Write code here that turns the phrase above into concrete actions end ならば(/^自分のtodoを書き込む$/) do pending # Write code here that turns the phrase above into concrete actions end 前提(/^todoの編集が終わった$/) do pending # Write code here that turns the phrase above into concrete actions end ならば(/^itemのバックアップを取る$/) do pending # Write code here that turns the phrase above into concrete actions end >>> ここでは,2つのscenarioと7つのstepが失敗しています. まだstep定義を記述していないので当たり前です. 一度cucumberを実行したのには理由があります. featureを書いた時点でcucumberを実行すると,ステップ定義の元となるコマンドを,cucumberが自動的に作成してくれるからです. 以下がcucumberから出力されたステップ定義の元となる部分です. <<< tcsh You can implement step definitions for undefined steps with these snippets: 前提(/^todoを編集したい$/) do pending # Write code here that turns the phrase above into concrete actions end もし(/^"([^"]*)"と入力する$/) do |arg1| pending # Write code here that turns the phrase above into concrete actions end ならば(/^editが開かれる$/) do pending # Write code here that turns the phrase above into concrete actions end ならば(/^自分のtodoを書き込む$/) do pending # Write code here that turns the phrase above into concrete actions end 前提(/^todoの編集が終わった$/) do pending # Write code here that turns the phrase above into concrete actions end ならば(/^itemのバックアップを取る$/) do pending # Write code here that turns the phrase above into concrete actions end >>> これをコピーして,featuresディレクトリの中でstep_definitionsディレクトリを作成し,その中にmy_todo_spec.rbを作成し,そこに貼付けます. ここでもう一度cucumberを実行してみると <<< tcsh /Users/nasubi/nasu% cucumber features/my_todo.feature # language: ja 機能: todoの更新を行う todoは更新していくものであり,新しく書いたり終わったものを消したいのでバックアップをとって,過去のtodoを残しておく シナリオ: コマンドを入力してtodoを更新していく # features/my_todo.feature:6 前提todoを編集したい # features/step_definitions/my_todo_spec.rb:1 TODO (Cucumber::Pending) ./features/step_definitions/my_todo_spec.rb:2:in `/^todoを編集したい$/' features/my_todo.feature:7:in `前提todoを編集したい' もし"my_todo --edit"と入力する # features/step_definitions/my_todo_spec.rb:5 ならばeditが開かれる # features/step_definitions/my_todo_spec.rb:9 かつ自分のtodoを書き込む # features/step_definitions/my_todo_spec.rb:13 シナリオ: コマンドを入力してバックアップをとる # features/my_todo.feature:12 前提todoの編集が終わった # features/step_definitions/my_todo_spec.rb:17 TODO (Cucumber::Pending) ./features/step_definitions/my_todo_spec.rb:18:in `/^todoの編集が終わった$/' features/my_todo.feature:13:in `前提todoの編集が終わった' もし"my_todo --store [item]"と入力する # features/step_definitions/my_todo_spec.rb:5 ならばitemのバックアップを取る # features/step_definitions/my_todo_spec.rb:21 2 scenarios (2 pending) 7 steps (5 skipped, 2 pending) 0m0.045s >>> と変化が出てきます. 2 scenarios (2 pending) 7 steps (5 skipped, 2 pending) これは2つのシナリオの内2つがpendingであり,7つのstepの内2つがpendingで5つがskipしたことを表しています. step_definitionsのmy_todo_spec.rbのpending部分を書き換えて,step_definitionsの記述を進めて行きます. cucumberが成功すると下記のような結果となります. <<< ruby /Users/nasubi/my_help% cucumber features/my_todo.feature # language: ja 機能: todoの更新を行う todoは更新していくものであり,新しく書いたり終わったものを消したいのでバックアップをとって,過去のtodoを残しておく シナリオ: コマンドを入力してtodoを更新していく # features/my_todo.feature:6 前提todoを編集したい # features/step_definitions/my_todo_spec.rb:2 もし"my_todo --edit"と入力する # features/step_definitions/my_todo_spec.rb:6 ならばeditが開かれる # features/step_definitions/my_todo_spec.rb:10 かつ自分のtodoを書き込む # features/step_definitions/my_todo_spec.rb:14 シナリオ: コマンドを入力してバックアップをとる # features/my_todo.feature:12 前提todoの編集が終わった # features/step_definitions/my_todo_spec.rb:18 もし"my_todo --store [item]"と入力する # features/step_definitions/my_todo_spec.rb:6 ならばitemのバックアップを取る # features/step_definitions/my_todo_spec.rb:22 2 scenarios (2 passed) 7 steps (7 passed) 0m0.030s >>> !!RSpec 次にRSpecを使って実際にtodoを更新する振る舞いをするコード書いていきます. そのための準備として,まずspecというディレクトリを作成し,my_todoというサブディレクトリを追加します. 次に,このサブディレクトリにtodo_spec.rbというファイルを追加します. 作業を進める過程で,lib/my_todo/my_todo.rbソースファイルとspec/my_todo/todo_spec.rbスペックファイルが1対1に対応するといった要領で, 並列のディレクトリ構造を築いていきます. この機能はmy_help --editと入力されれば,~/.my_help/my_todo.ymlが開かれるのでその振る舞いをするコードを書きます. まずtodo_spec.rbは下記の通りになります. <<< ruby require 'spec_helper' module Mytodo describe Todo do describe "#open" do it "open file my_todo.yml" end end end >>> describe()メソッドは,RSpecのAPIにアクセスしてRSpec::Core::ExampleGroupのサブクラスを返します. ExampleGroupクラスはオブジェクトに期待される振る舞いのサンプルを示すグループです. it()メソッドはサンプルを作成します. このスペックを実行するために,specディレクトリにspec_helper.rbを追加します. 中身は下記の通りです. <<< ruby $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'my_help' require 'todo' >>> これで事前準備は完成でコードを書いていきます. 完成したコードを下記の通りです. <<< ruby require 'spec_helper' module Mytodo describe Todo do describe "#open" do it "open file my_todo.yml" do system("emacs ~/.my_help/my_todo.yml") end end end end >>> これでrspecを実行すると以下のような結果が表示されます. <<< tcsh /Users/nasubi/my_help% rspec spec --color my_help command version option should be successfully executed should have output: "my_help 0.4.3" help option should be successfully executed should have output: "Usage: my_help [options]\n -v, --version show program Version.\n -l, --list... install local after edit helps\n --delete NAME delete NAME help" Mytodo::Todo #open open file my_todo.yml Finished in 3.87 seconds (files took 0.30703 seconds to load) 5 examples, 0 failures >>> これでtodoを更新するときの振る舞いのテストはうまく成功しました. この方法でBDDでmy_helpのテスト開発を行い,仕様を決定していきました.