在ruby世界中有許多好用的測試框架,rspec算是其中比較流行的一個。rspec使用了和直接測試方法的不同測試思路——測試應用的行爲。下面我就來解釋一下怎樣實用rspec來測試應用。
Rspec入門
describe Hash do
end
it塊被用來編寫具體測試代碼。下面這個例子就是爲Hash類編寫的一個spec測試:
describe Hash do
it "should return a blank instance" do
Hash.new.should == {}
end
end
上面就是Rspec的基礎示例了。在用命令gem install rsepc安裝好rspec,然後將上面的代碼放入hash_spec.rb文件,鍵入下面的命令執行測試:
$ rspec hash_spec.rb
測試的輸出結果如下:.
Finished in 0.11021 seconds
1 example, 0 failures
你也可以實用before和after來設置測試前後需要執行的代碼,在before塊和after塊中的設置在describe塊中也有效:
describe Hash do
before do
@hash = Hash.new({:hello => 'world'})
end
it "should return a blank instance" do
Hash.new.should == {}
end
it "hash the correct information in a key" do
@hash[:hello].should == 'world'
end
end
上面的代碼會在每個測試塊執行前創建一個 @hash變量。before塊可以攜帶兩個參數——all(一次性爲所有的測試塊設置變量)或each(每個測試塊單獨設置變量)。after塊和before塊一樣也有這兩個參數,不同的僅僅是after是在測試塊代碼之後執行。在前一個測試執行結束需要銷燬起狀態時after塊很有用。
RSpec術語
describe MyClass do
describe ".class_method_1" do
end
describe "#instance_method_1" do
end
end
context方法主要用來將一羣具有相同上下文的測試集合在一起。當使用複雜的setup和teardown代碼來設置對象時特別有用。下面以真實生活中的漢堡類爲例。
describe Burger do
describe "#apply_ketchup" do
context "with ketchup" do
before do
@burger = Burger.new(:ketchup => true)
@burger.apply_ketchup
end
it "sets the ketchup flag to true" do
@burger.has_ketchup_on_it?.should be_true
end
end
context "without ketchup" do
before do
@burger = Burger.new(:ketchup => false)
@burger.apply_ketchup
end
it "sets the ketchup flag to false" do
@burger.has_ketchup_on_it?.should be_false
end
end
end
end
整理一下
describe Burger do
describe "#apply_ketchup" do
context "with ketchup" do
let(:burger) { Burger.new(:ketchup => true) }
before { burger.apply_ketchup }
it "sets the ketchup flag to true" do
burger.has_ketchup_on_it?.should be_true
end
end
context "without ketchup" do
let(:burger) { Burger.new(:ketchup => false) }
before { burger.apply_ketchup }
it "sets the ketchup flag to false" do
burger.has_ketchup_on_it?.should be_false
end
end
end
end
代碼變得好看了些,但我們可以使用subject方法來進一步簡化。subject指定的對象是rspec目前正在進行的測試針對的對象。下面我將結合specify方法使用它。specify方法和it十分相似,唯一區別是specify將測試內容作爲測試描述:
describe Burger do
describe "#apply_ketchup" do
subject { burger }
before { burger.apply_ketchup }
context "with ketchup" do
let(:burger) { Burger.new(:ketchup => true) }
specify { subject.has_ketchup_on_it?.should be_true }
end
context "without ketchup" do
let(:burger) { Burger.new(:ketchup => true) }
specify { subject.has_ketchup_on_it?.should be_false }
end
end
end
按照rspec的約定,它會尋找以has開頭並且以問號結尾的方法,下面是最終的burger_spec.rb:
class Burger
attr_reader :options
def initialize(options={})
@options = options
end
def apply_ketchup
@ketchup = @options[:ketchup]
end
def has_ketchup_on_it?
@ketchup
end
end
describe Burger do
describe "#apply_ketchup" do
subject { burger }
before { burger.apply_ketchup }
context "with ketchup" do
let(:burger) { Burger.new(:ketchup => true) }
it { should have_ketchup_on_it }
end
context "without ketchup" do
let(:burger) { Burger.new(:ketchup => false) }
it { should_not have_ketchup_on_it }
end
end
end
漢堡需要的不僅僅是番茄醬
附:RSpec斷言規則
[].should be_empty => [].empty? #passes
[].should_not be_empty => [].empty? #fails
除了用"be_"來前綴斷言方法,也可以用"be_a_"和"be_an_"前綴,使得代碼讀起來更加自然:
"a string".should be_an_instance_of(String) =>"a string".instance_of?(String) #passes
3.should be_a_kind_of(Fixnum) => 3.kind_of?(Numeric) #passes
3.should be_a_kind_of(Numeric) => 3.kind_of?(Numeric) #passes
3.should be_an_instance_of(Fixnum) => 3.instance_of?(Fixnum) #passes
3.should_not be_instance_of(Numeric) => 3.instance_of?(Numeric) #fails
Rspec也會爲諸如“has_key?”之類的斷言創建匹配器,要使用該特性,在斷言對象上使用 should have_key(:key) 就可以了,rspec會自動在對象上調用has_key?(:key)。如:
{:a => "A"}.should have_key(:a) => {:a => "A"}.has_key?(:a) #passes
{:a => "A"}.should have_key(:b) => {:a => "A"}.has_key?(:b) #fails
還有一些常見的斷言方法如: be be_close change eql have be_true be_false be_nil include raise_error respond_to throw_symbol 等等