Rspec是ruby的測試框架之一予跌。
Mock和stub都屬于Test double,用于測試時温治,模擬特定的方法或者對象的值或行為剧劝。
在Rspec中提供了這3種方法(gem rspec-mocks)。
Test Double
Test Double是一個模擬對象舰攒,在代碼中模擬系統(tǒng)的另一個對象败富,方便測試。
例如我們有個智能書架(Bookshelf)摩窃,可以自動統(tǒng)計(jì)書(book)的總重量(weight_of_books)兽叮。在測試這個方法的時候,需要我們有book這個對象猾愿,而Book類仍在開發(fā)中鹦聪,不能被我們使用。
book = double("Book")
這樣我們就有了一個book對象. 但現(xiàn)在book沒有任何方法蒂秘,這是空的類泽本,我們可以使用stub等方法為他創(chuàng)建假方法。
同時姻僧,也可以直接創(chuàng)建一個有屬性的book對象规丽。
book = double("Book", weight: 80)
book1 = double("Book", weight: 80)
book2 = double("Book", weight: 20)
bookshelf = Bookshelf.new
bookshelf.add(book1)
expect(bookshelf.weight_of_books).to eq(80)
bookshelf.add(book2)
expect(bookshelf.weight_of_books).to eq(100)
這樣,我們就測試了bookshelf的方法撇贺。
instance_double 是RSpec 3之后替代double赌莺,并支持verifying double,意思是當(dāng)使用stub的方法后松嘶,RSpec還會去驗(yàn)證被double的真實(shí)對象是否具有這個方法艘狭。
Method Stubs
Stub 既可以模擬假對象(test double)的方法,也可以模擬真對象(real object)的方法。RSpec-mock提供下面3種創(chuàng)建method stub的方法巢音。
allow(book).to receive(:title) { "The RSpec Book" } # 第一個參數(shù)(book)是對象遵倦,第二個參數(shù)是方法, 第三個參數(shù)是方法的返回值
allow(book).to receive(:title).and_return("The RSpec Book")
allow(book).to receive_messages(
:title => "The RSpec Book",
:subtitle => "Behaviour-Driven Development with RSpec, Cucumber, and Friends")
上面等同于:
book = double("book", :title => "The RSpec Book" )
stub返回多個值
allow(die).to receive(:roll).and_return(1, 2, 3)
die.roll # => 1
die.roll # => 2
die.roll # => 3
die.roll # => 3
die.roll # => 3
每次調(diào)用stub的方法港谊,會按順序依次返回骇吭。
Mock
Mock與Stub的區(qū)別在于, Mock是對象層面的歧寺,Stub是方法層面燥狰。Mock對象同時支持method的stub和 消息驗(yàn)證(message expectation)
通常Rspec使用expect去驗(yàn)證message是否工作。
validator = double("validator")
expect(validator).to receive(:validate) { "02134" }
zipcode = Zipcode.new("02134", validator)
zipcode.valid?
如果validate消息在用例執(zhí)行到最后有被接收到斜筐,則驗(yàn)證成功龙致,否則失敗。
新老版本差異
Rspec 老新語法差異(前為老顷链,后為新)目代,前后的作用基本一樣:
- stub vs allow
- should_recevice vs expect().to recevice
- any_instance (已遺棄)
- stub_chain (已遺棄)
- unstub vs and_call_original
1. stub vs allow
對于should
方法做斷言,通常對應(yīng)的stub方法:
describe "a stubbed implementation" do
it "works" do
object = Object.new
object.stub(:foo) do |arg|
if arg == :this
"got this"
elsif arg == :that
"got that"
end
end
object.foo(:this).should eq("got this")
object.foo(:that).should eq("got that")
end
end
對于RSpec3的 expect
方法嗤练,對應(yīng)的stub方法:
describe "a stubbed implementation" do
it "works" do
object = Object.new
allow(object).to receive(:foo) do |arg|
if arg == :this
"got this"
elsif arg == :that
"got that"
end
end
expect(object.foo(:this)).to eq("got this")
expect(object.foo(:that)).to eq("got that")
end
end
2. should_recevice vs expect().to recevice
老版本的Rspec(2.14)榛了, 使用should_receive. RSpec3使用了expect
Session.should_receive(:get).with('test_session_id').and_return(mock_session)
expect(Session).to receive(:get).with('test_session_id').and_return(mock_session)
3. any_instance (已遺棄)
Incident.any_instance.stub(:field_definitions).and_return([])
意思是Incident類的所有實(shí)例都具備stub的方法,不過該方法已經(jīng)被遺棄煞抬。
RSpec.describe "Expecting a message on any instance of a class" do
before do
Object.any_instance.should_receive(:foo)
end
it "passes when an instance receives the message" do
Object.new.foo
end
it "fails when no instance receives the message" do
Object.new.to_s
end
end
上述結(jié)果:
2 examples, 1 failure
Exactly one instance should have received the following message(s) but didn't: foo
任意new的Object類的實(shí)例都會有foo方法霜大,當(dāng)使用should_recevice
時,所有的Object的實(shí)例都必須響應(yīng)foo方法革答,否則就會報(bào)錯战坤。
stub_chain (已遺棄)
支持鏈?zhǔn)椒椒ǖ膕tub,現(xiàn)在已經(jīng)不推薦使用残拐。
example "using a string and a block" do
dbl.stub_chain("foo.bar") { :baz }
expect(dbl.foo.bar).to eq(:baz)
end
unstub vs and_call_original
unstub
將之前實(shí)例stub的方法disable掉途茫,聲明之后,stub的方法溪食,將被原有的方法替代或者不存在囊卜。
RSpec.describe "Unstubbing a method" do
it "restores the original behavior" do
string = "hello world"
string.stub(:reverse) { "hello dlrow" }
expect {
string.unstub(:reverse)
}.to change { string.reverse }.from("hello dlrow").to("dlrow olleh")
end
end
and_call_original
是類似的功能仔掸,使用with
可以讓特定的參數(shù)輸入時洼哎,仍然適用stub的方法返回值
require 'calculator'
RSpec.describe "and_call_original" do
it "can be overriden for specific arguments using #with" do
allow(Calculator).to receive(:add).and_call_original
allow(Calculator).to receive(:add).with(2, 3).and_return(-5)
expect(Calculator.add(2, 2)).to eq(4)
expect(Calculator.add(2, 3)).to eq(-5)
end
end
配置返回方式
以下是 RSpec 3.4 版本特性
- and_return 返回值
- and_raise 拋出異常
- and_throw 拋出symbol
- and_yield 讓方法接受塊參數(shù)
- and_call_original 調(diào)用原有的方法
- and_wrap_original 使用塊修改原有的方法