RSpec on Railsを使ってみた
WEB+DB PRESS Vol.45のBDD特集を教科書に、rspec-railsを扱ってみた。教科書があることが前提なので、これを買って読んでいない人には意味不明な記事になると思う。
ただし、完全に本と同じやり方をしてはいない。最新バージョンを使うからインストール方法などいろいろと違うし、Seleniumも使わない。また、本では触れていない事柄についても少し調べた。そんなわけで、本の通りにやらなかったところをメモ書きしておきたい。
環境は次の通り。
- Ruby 1.8.7
- Ruby on Rails 2.1.0
- RSpec 1.1.4
- rspec-rails 1.1.4
- Rcov 0.8.1.2.0
- Git 1.5.5.4
rspec-railsインストール
せっかくなんで、流行のGitを使った。たぶんRubyforgeに行ってrspec-rails-1.1.4.tgzを落としてきて使っても問題ない。
最新の安定版1.1.4を使いたいのだが、GitHubを見に行ったところ、どうやらバージョンを指定してインストールするためには、たくさんコマンドを叩く必要があるらしい。
$ cd vendor/plugins $ git clone git://github.com/dchelimsky/rspec.git $ git clone git://github.com/dchelimsky/rspec-rails.git $ cd rspec $ git checkout 1.1.4 $ cd ../rspec-rails $ git checkout 1.1.4 $ cd .. $ rm -rf rspec/.git $ rm -rf rspec-rails/.git $ cd ../../
gitでリポジトリのクローンを取得、バージョン(というかタグ)指定して1.1.4のコードを取得、そこまでできたらリポジトリは不要なので削除、という流れ。
次のコマンドを実行して、基本的なファイルの生成を行う。
$ script/generate rspec
ちなみにバージョン指定せずEdgeをインストールするのであれば実行するコマンドの数が減るのだが、試してみたところファイル生成時にdefault_valueメソッドが無いとのエラーが出てしまう。
Model作成
$ script/generate rspec_model Post content:text name:string
ここらでDBを作っておく。SQLite3だとrake db:createがいらない。
$ rake db:migrate
あとは本にあるとおりにコードを書いていけば、ちゃんとテストできる。fixtureとかは好き勝手につくる。もっとも、作らなくてもとりあえずテストはグリーンになるが。
テスト実行時にDEPRECATION WARNINGが出てしまうのだが、とりあえず不都合は無いので放置。rspec-rails内のコードがdeprecatedなメソッドを呼び出しているらしいが、まぁそのうち対応されるだろう。
Controller作成
これも本の通りでOK。
View作成
本にあるspecコードなんだが。
it "は、投稿の表示(div.post)を含むこと" do response.should have_tag('div') do |div|
これ、定義文(div.post)とコード(div)が一致していないのでは?たぶんこう書くべきだと思うのだが。
it "は、投稿の表示(div.post)を含むこと" do # response.should have_tag('div') do |div| response.should have_tag('div.post') do |div| # こっちか?
さてテストしてみようかな、と思うわけだが、本にはViewのspecは書いてあるもののテスト対象のhtml.erbファイルについては何も書かれていない。そんなわけで、app/views/posts/list.html.erbを適当に作る。こんな感じでいいや。
<h1>BBS for RSpec trial</h1> <% @posts.each do |post| %> <div class="post"> <div class="name"><%=h post.name %></div> <div class="content"><%=h post.content%></div> </div> <hr/> <% end %>
テストしてみると、見事にエラー。
1) ActionView::TemplateError in 'posts/listテンプレートがpostのリストを渡されたとき は、投稿の表示(div.post)を含むこと' Mock 'post 1' received unexpected message :name with (no args)
本にある通りのspecだと、mockにname, contentメソッド呼び出しがあることを教えていないからだと思われる。ちゃんと教えてやるか。
require File.dirname(__FILE__) + '/../../spec_helper' describe "posts/listテンプレートがpostのリストを渡されたとき" do before { assigns[:posts] = @posts = (1..5).map { |i| mock("post #{i}") } } # このbeforeを足した。 before { @posts.each_with_index do |p,i| p.should_receive(:name).and_return("name of #{i}") p.should_receive(:content).and_return("content of #{i}") end } before { render 'posts/list' } ...
なんかとてつもなくいい加減な値を返しているので、ほんとにこんなんでいいのか?という疑問が湧いてしまう…。
気を取り直してテストしてみると、今度は無事グリーンに。
しかし、ここでちょいとrspec-railsのrdocを眺めてみると、with_tag / without_tagというメソッド発見。
もしかして…
it "は、投稿の表示(div.post)を含むこと" do response.should have_tag('div.post') do |div| div.should have_tag('div.name') end end
ここは書き換えられるのか。
it "は、投稿の表示(div.post)を含むこと" do response.should have_tag('div.post') do with_tag('div.name') end end
試した感じ、どちらも同じ動作をしていそう。しかし、with_tagの方がわかりやすい。
カバレッジ
ところで、どうせテストするならやっぱりカバレッジを知りたいところ。ヘルプを見た感じ、Rcovを簡単に使えそうだ。
まずはRcovをインストール。
$ sudo gem install rcov
続けて、カバレッジ取得。
$ rake spec:rcov
これでcoverage/にレポートがHTML形式で生成されるので、これを適当なブラウザで眺めるとよし。
RSpecのHTMLレポート
RSpecはテスト結果をHTMLフォーマットで出力する機能をデフォルトで持っている。しかし、rspec-railsではプレーンテキストベースのprogressフォーマットとなっている。これ、切り替えできないものだろうか。
単にHTMLフォーマットに変更してしまうのであれば、spec/spec.optsの中身を書き換えれば済むことだ。しかし、HTMLフォーマットのレポートは基本的に継続的インテグレーションしている場合のビルドレポートの一部として見たいだけで、通常開発時は元の通りprogressフォーマットでレポートを標準出力に出してほしい。つまり、両方使いたい。
これは簡単な対処法は無いみたいで、どうも自分で独自にタスクを定義するしかなさそう。そんなわけで、lib/tasks/rspec_ext.rakeを作成。中身はこんな感じ。
rspec_base = File.expand_path(File.dirname(__FILE__) + '/../../vendor/plugins/rspec/lib/') $LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base) require 'spec/rake/spectask' spec_prereq = File.exist?(File.join(RAILS_ROOT, 'config', 'database.yml')) ? "db:test:prepare" : :noop task :noop do end namespace :spec do desc "Generate HTML report for Rspec." Spec::Rake::SpecTask.new(:html_report => spec_prereq) do |t| t.fail_on_error = false t.spec_opts = ["--format", "h:spec/report.html"] t.spec_files = FileList['spec/**/*_spec.rb'] end end
noopタスクの定義部分までは、vendor/plugins/rspec-rails/tasks/rspec.rakeの中身をコピーした。これで次のようにコマンドを叩けば…。
$ rake spec:html_report
spec/report.htmlが生成されるわけである。lib/tasks/rspec_ext.rakeの中身をそのままrspec.rakeの中に追記してもいいような気もするが、どうもプラグイン本体に手を入れるのには抵抗がある。