最近は Ruby のテストに興味があっていろいろ試しています。
今気になっているのは RSpec と Cucumber の2つ。今回はまず RSpec を色々触ってみたのでそのときのログをメモってみます。RSpec については RSpec + Autotest::screen = 最高の開発環境 でも書きましたが、BDD(振舞駆動開発)のフレームワークで、describe と it という2つのメソッドを利用します。describe にテストしたい振舞を書き、it にはそのときに満たすべき仕様を書くという感じです。今回は Rails で RSpec を使ったテストを書いてみましたよ。(=゚ω゚)ノ
事前準備として、rspec と rspec-rails と Zentest(テストを自動で走らせるため。この中に autotest が含まれています)をインストールします。
まず、テストに使う model はこんな感じにしてみました。blog has many tags ( tag belongs to blog ) です。あとは get_hatebu っていうはてブ数を取得してくれるメソッドだけ実装。
次はspecファイル(RSpec のテストコードを書いたファイル)です。テスト(というか仕様)は7つ作りました。1つのitブロックが1つのテスト(仕様)に対応します。beforeには、各itブロック実行毎に走らせたい事前処理を書きます。
spec/models/blog_spec.rb
fixtureファイルはこんな感じー。
spec/fictures/blogs.yml
何度も言いますが、itブロックでは仕様を定義しています。つまり、こうなるべき(should)という期待結果があるはずです。それをコードの中でごく自然に書けるようにするため、必ずこのように should (もしくは should_not) という形で期待結果と値を比較するわけです。分かりやすいですね〜。
== とか have とかがマッチャーです。マッチャーについては RSpecの標準Matcher一覧表 - Text::Easyhacking がまとまっていてオススメですよ!
んで、実行結果はこのようになります。僕の場合は Autotest::screen を使っているので、自動でテストが走り screenのステータスライン上(画面右下)に結果が表示されます。autotest の設定ファイルはここに張ってあるので良かったら参考にしてみてください。
Rails を使っている場合 spec ディレクトリの中に spec.opts というファイルがあります。これが RSpec の設定ファイルです。このファイルを↓のようにしておくことで、結果が↑のようになって見やすいです。
あと、Zentest の中の autotest を使っていて、ファイル(xxx.rb)を変更すると対応するspecファイル(xxx_spec.rb)のテストを実行してくれるのはとても便利なんですが、controller のテストとかはあんまり要らないかなぁと思ったので、このようにいじって model しか自動でテストを走らせないようにしています。
ただ、元ファイルを弄るっていうのが何か凄くアレです。。。「こうすれば出来るよ」など知っている方いましたら是非教えてください _ノ乙(、ン、)_
autotest.rb
最後に、今回色々と触ってみて「なるほど〜」と思ったことをつらづらとまとめておきます。参考に。
やっぱり RSpec でテストを書くと分かりやすいし、見た目もすっきりします。autotest を使って自動でテストを走らせるのもとてもいいです。この2つを同時に使うと素晴らしいですね〜。
さぁ、次は Cucumber について書きますよ!!
今気になっているのは RSpec と Cucumber の2つ。今回はまず RSpec を色々触ってみたのでそのときのログをメモってみます。RSpec については RSpec + Autotest::screen = 最高の開発環境 でも書きましたが、BDD(振舞駆動開発)のフレームワークで、describe と it という2つのメソッドを利用します。describe にテストしたい振舞を書き、it にはそのときに満たすべき仕様を書くという感じです。今回は Rails で RSpec を使ったテストを書いてみましたよ。(=゚ω゚)ノ
事前準備として、rspec と rspec-rails と Zentest(テストを自動で走らせるため。この中に autotest が含まれています)をインストールします。
sudo gem install rspec rspec-rails Zentest
まず、テストに使う model はこんな感じにしてみました。blog has many tags ( tag belongs to blog ) です。あとは get_hatebu っていうはてブ数を取得してくれるメソッドだけ実装。
# models/blog.rb
class Blog < ActiveRecord::Base
has_many :tags
require "xmlrpc/client"
require "uri"
require "net/http"
Net::HTTP.version_1_2
HATENA_XMLRPC_URL = "http://b.hatena.ne.jp/xmlrpc"
def self.get_hatebu(url)
uri = URI.parse(HATENA_XMLRPC_URL)
server = XMLRPC::Client.new(uri.host, uri.path, uri.port)
return server.call("bookmark.getTotalCount", url)
end
end
# models/tag.rb
class Tag < ActiveRecord::Base
belongs_to :blog
end
次はspecファイル(RSpec のテストコードを書いたファイル)です。テスト(というか仕様)は7つ作りました。1つのitブロックが1つのテスト(仕様)に対応します。beforeには、各itブロック実行毎に走らせたい事前処理を書きます。
spec/models/blog_spec.rb
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe "Blogデータを保存する場合" do
before do
@blog = Blog.new( :url => "http://sasata299.com" )
end
it "新たなデータが作成できること" do
@blog.save.should be_true # 成功すればtrue、失敗すればfalse
end
it "DBに記事が1件増えること" do
Proc.new { @blog.save }.should change(Blog, :count).by(1)
end
end
describe "Blogデータを一覧表示する場合" do
fixtures :blogs, :tags
before do
@blog = Blog.find(:all)
end
it "全部で3件のブログが登録されていること" do
@blog.size.should == 3
end
it "sasata299's blog はタグを2つ持つこと" do
pending("hoge")
blogs(:my_blog).should have(2).tags
end
it "sasata299's blog は perl と mysql というタグを持つこと" do
blogs(:my_blog).tags.should == [ tags(:tag_1), tags(:tag_4) ]
end
end
describe "はてブ数を取得する場合" do
it "1つでもブックマークがあれば、その数を取得できること" do
Blog.get_hatebu("http://blog.livedoor.jp/sasata299/").should >= 290
end
it "1つもブックマークされていなければ、0が返ること" do
Blog.get_hatebu("http://blog.livedoor.jp/sasata2999/").should == 0
end
end
fixtureファイルはこんな感じー。
spec/fictures/blogs.yml
my_blog: id: 1 name: "sasata299's blog" url: http://blog.livedoor.jp/sasata299/ zozom: id: 2 name: zozomのページ url: http://www.zozom.net sasata299: id: 3 name: sasata299のページ url: http://sasata299.com
何度も言いますが、itブロックでは仕様を定義しています。つまり、こうなるべき(should)という期待結果があるはずです。それをコードの中でごく自然に書けるようにするため、必ずこのように should (もしくは should_not) という形で期待結果と値を比較するわけです。分かりやすいですね〜。
hoge.should マッチャー
or
fuga.should_not マッチャー
== とか have とかがマッチャーです。マッチャーについては RSpecの標準Matcher一覧表 - Text::Easyhacking がまとまっていてオススメですよ!
んで、実行結果はこのようになります。僕の場合は Autotest::screen を使っているので、自動でテストが走り screenのステータスライン上(画面右下)に結果が表示されます。autotest の設定ファイルはここに張ってあるので良かったら参考にしてみてください。
/usr/bin/ruby -S spec/models/blog_spec.rb -O spec/spec.opts はてブ数を取得する場合 - 1つもブックマークされていなければ、0が返ること - 1つでもブックマークがあれば、その数を取得できること Blogデータを一覧表示する場合 - sasata299's blog は perl と mysql というタグを持つこと - sasata299's blog はタグを2つ持つこと (PENDING: hoge) - 全部で3件のブログが登録されていること Blogデータを保存する場合 - DBに記事が1件増えること - 新たなデータが作成できること Pending: Blogデータを一覧表示する場合 sasata299's blog はタグを2つ持つこと (hoge) Called from spec/models/blog_spec.rb:30 Finished in 1.113638 seconds 7 examples, 0 failures, 1 pending ==============================================================================
Rails を使っている場合 spec ディレクトリの中に spec.opts というファイルがあります。これが RSpec の設定ファイルです。このファイルを↓のようにしておくことで、結果が↑のようになって見やすいです。
--colour --format specdoc # ココを変更しました --loadby mtime --reverse
あと、Zentest の中の autotest を使っていて、ファイル(xxx.rb)を変更すると対応するspecファイル(xxx_spec.rb)のテストを実行してくれるのはとても便利なんですが、controller のテストとかはあんまり要らないかなぁと思ったので、このようにいじって model しか自動でテストを走らせないようにしています。
ただ、元ファイルを弄るっていうのが何か凄くアレです。。。「こうすれば出来るよ」など知っている方いましたら是非教えてください _ノ乙(、ン、)_
autotest.rb
def find_files
result = {}
targets = self.find_directories + self.extra_files
self.find_order.clear
targets.each do |target|
order = []
Find.find(target) do |f|
Find.prune if f =~ self.exceptions
next if test ?d, f
next if f =~ /(swp|~|rej|orig)$/
next if f =~ /\/\.?#/
next if f !~ /model/ # この行を追加!
filename = f.sub(/^\.\//, '')
result[filename] = File.stat(filename).mtime rescue next
order << filename
end
self.find_order.push(*order.sort)
end
return result
end
最後に、今回色々と触ってみて「なるほど〜」と思ったことをつらづらとまとめておきます。参考に。
# こんな風に指定すると、fixturesを利用できる
# テスト時にfixturesのデータをテーブルにinsertし、終わったら消してくれる
fixtures :blogs, :tags
# fixturesのデータにアクセスするにはこのような指定ができる
blogs(:my_blog)
tags(:tag_4)
# テストをpending(保留)したいときにはpendingメソッドを指定する
pending(msg)
# changeマッチャが便利!!
# Procオブジェクト(xxx)を評価したら、yyyの値がzzzだけ変化する
# byの他にも、fromとかtoもある
Proc.new { xxx }.should change(Obj, :yyy).by(zzz)
やっぱり RSpec でテストを書くと分かりやすいし、見た目もすっきりします。autotest を使って自動でテストを走らせるのもとてもいいです。この2つを同時に使うと素晴らしいですね〜。
さぁ、次は Cucumber について書きますよ!!
