{{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のバックアップをとる この振る舞いがきちんとできているのかをBDDでテストを書いていきます. !!Cucumber 以下はtodoの更新を行うときのfeatureです. まず,適当なディレクトリにfeaturesというディレクトリを作成します. 次に先ほど作成した,featuresディレクトリにmy_todo.featureを作成します. <<< tcsh # language: ja 機能: todoの更新を行う 自分のするべきことを書き込むためにtodoを更新する シナリオ: コマンドを入力してtodoを更新していく 前提 todoを編集したい もし "my_todo --edit"と入力する ならば editが開かれる かつ 自分のtodoを書き込む >>> 機能とは,このシステムの機能のことを記述します.ここでは,todoを更新するシステムですので,「todoの更新を行う」です. 機能の下には,機能の補足説明を記述します.機能の補足説明では,ルールがないので自分がわかりやすいように,記述するのが常識です. シナリオは,その名の通りtodoを更新する時のユーザの行動やシステム振る舞いを前提,もし,ならば,かつ,しかしに分類して記述します. ここまでfeatureが記述できたら,次はcucumberコマンドを実行してみます. コマンドは以下の通りです. nasu% cucumber features/my_todo.feature featuresディレクトリにあるmy_todo.featureファイルをcucumberで実行するという意味です. 実行すると図{{ref(fig:003)}}のようになります. !!!caption:(fig:003)step定義をする前のcucumberの実行結果. {{attach_view(my_help_nasu.003.jpeg,my_help_nasu)}}) ここでは,1つのscenarioと4つのstepが失敗しています. まだstep定義を記述していないので当たり前です. 一度cucumberを実行したのには理由があります. featureを書いた時点でcucumberを実行すると,ステップ定義の元となるコマンドを,cucumberが自動的に作成してくれるからです. 以下がcucumberから出力されたステップ定義の元となる部分です. <<< tcsh 前提(/^todoを編集したい$/) do end もし(/^"([^"]*)"と入力する$/) do |command| end ならば(/^editが開かれる$/) do end ならば(/^自分のtodoを書き込む$/) do end >>> これをコピーして,featuresディレクトリの中でstep_definitionsディレクトリを作成し,その中にmy_todo_spec.rbを作成し,そこに貼付けます. ここでもう一度cucumberを実行してみると図{{ref(fig:004)}}のように変化が出てきます. !!!caption:(fig:004)defaultのstep定義を貼り付けた後のcucumberの実行結果. {{attach_view(my_help_nasu.004.jpeg,my_help_nasu)}} これは1つのシナリオがあり,1つがpendingであり,4つのstepの内1つがpendingで3つがskipしたことを表しています. step_definitionsのmy_todo_spec.rbのpending部分を書き換えて,step_definitionsの記述を進めていきます. まず,「前提」を見てみるとmy_helpが何か振る舞いをすることはありません. よって,このままにしておきます. 「もし」もユーザが入力するコマンドであり,my_helpが何か振る舞いをすることはないのでこのままにしておきます. 次に,「ならば」を見てみるとmy_helpがeditを開くという振る舞いをしています. ステップ定義では,あれば良いと思うコードを記述するので私は下記のように記述しました. <<< tcsh ならば(/^editが開かれる$/) do Mytodo::Edit.new.open end >>> pendingの部分が書けたので,もう一度cucumber features/my_todo.featureを実行します. すると,図{{ref(fig:005)}}のような結果が返ってきました. !!!caption:(fig:005)pendingのstep定義を書き込んだ後のcucumberの実行結果. {{attach_view(my_help_nasu.005.jpeg,my_help_nasu)}} Cucumberは,エラーが出たステップのすぐ後ろにエラーを表示してくれます. ここでCucumberでエラーが出たので,この「ならば editが開かれる」のシナリオに注目してRSpecに進むことにします. !!RSpec 次にRSpecを使って実際にtodoを更新する振る舞いをするコード書いていきます. そのための準備として,まずspecというディレクトリを作成し,my_todoというサブディレクトリを追加します. 次に,このサブディレクトリにtodo_spec.rbというファイルを追加します. この機能は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()メソッドはサンプルを作成します. 完成したコードを下記の通りです. <<< 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 >>> specディレクトリのmy_todoディレクトリをrspecで実行すると図{{ref(fig:006)}}結果がでました. [[--color]]を付け加えるとわかりやすく色づけをしてくれて,見やすくなります. !!!caption:(fig:006)pendingのstep定義を書き込んだ後のcucumberの実行結果. {{attach_view(my_help_nasu.006.jpeg,my_help_nasu)}} 図{{ref(fig:006)}}を見るとエラーが出てしまっているのがわかります. `': uninitialized constant Mytodo::Edit (NameError) 上記のエラーを解決するために,specディレクトリの一つ上の構造のディレクトリにlibディレクトリを作成します. その中にmy_todoというディレクトリを作成し,my_todo.rbを作成します. 構造を表示すると以下のようになります. /Users/nasubi/my_help/lib/my_todo% ls my_todo.rb my_todo.rbに先ほどのエラーでMytodoというmoduleがないといわれているので, ここで作成します. /Users/nasubi/my_help/lib/my_todo/my_todo.rbの中に以下のコードを記述します. <<< tcsh module Mytodo class Edit def open end end end >>> また,これをrequireしないといけないので,lib/todo.rbとして,以下を追加します. <<< require 'my_todo/my_todo' >>> これだけでは,rspecがlib/my_todo/my_todo.rbを読み込んでいないため,このスペックを実行するために,specディレクトリにspec_helper.rbに以下を追加します. <<< tcsh $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'todo' >>> これでもう一度rspecを実行してみると図{{ref(fig:007)}}の結果が返ってきました. !!!caption:(fig:007)redを修正してgreenになったrspecの実行結果 {{attach_view(my_help_nasu.007.jpeg,my_help_nasu)}} 図{{ref(fig:007)}}を見ると,エラーが消えて成功しているのがわかります. これで「ならば editが開かれる」のシナリオのRSpec部分が成功しました. Red, Greenと進めたので次はRefactoringをするのですが,ここではあまり必要のないので省略します. RSpecが終わったので,Cucumberに戻ります. 先ほどlibディレクトリでlib/my_todo/my_todo.rbを作成したので,cucumberでも読み込むために,以下を作成します. /Users/nasubi/my_help/features/support/env.rb env.rbの中は以下の通りです. <<< ruby $LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__) require 'todo' >>> これでcucumberもlib/の中身を読み取ってくれます. もう一度cucumberを実行してみると,図{{ref(fig:008)}}の結果が返ってきました. !!!caption:(fig:008)libの中身を読み込むようにしたcucumberの実行結果. {{attach_view(my_help_nasu.008.jpeg,my_help_nasu)}} エラーが消えています. これでBDDが成功しました. 残りの「自分のtodoを書き込む」もmy_helpが何か振る舞いをするわけではないので,「コマンドを入力してtodoを更新する」シナリオ全てのテスト開発が終わりました. このようにBDDでmy_helpのテスト開発を行っていきました. !!.rspec options 図{{ref(fig:006)}}の時に述べたように,[rspec spec --color]で実行すると成功すればGreenで表示され,失敗すればRedで表示されます. 結果に色がついていると見やすく,どのような結果が出ているかが一目瞭然です. しかし,毎回[--color]のコマンドを打ち込むのが面倒になってきます. そこで,カレントディレクトリに.rspecを用意し, <<< --format documentation --color >>> のようにすることで,rspecで実行するときにこのファイルを読み込み,[--color]を入力しなくても実行されます.