RSpec, Test Double, Mock, and Stub

Rspec是ruby的測(cè)試框架之一。

Mockstub都屬于Test double,用于測(cè)試時(shí),模擬特定的方法或者對(duì)象的值或行為。

在Rspec中提供了這3種方法(gem rspec-mocks)。

Test Double


Test Double是一個(gè)模擬對(duì)象,在代碼中模擬系統(tǒng)的另一個(gè)對(duì)象,方便測(cè)試。

例如我們有個(gè)智能書(shū)架(Bookshelf),可以自動(dòng)統(tǒng)計(jì)書(shū)(book)的總重量(weight_of_books)。在測(cè)試這個(gè)方法的時(shí)候,需要我們有book這個(gè)對(duì)象,而B(niǎo)ook類(lèi)仍在開(kāi)發(fā)中,不能被我們使用。

book = double("Book")

這樣我們就有了一個(gè)book對(duì)象. 但現(xiàn)在book沒(méi)有任何方法,這是空的類(lèi),我們可以使用stub等方法為他創(chuàng)建假方法。

同時(shí),也可以直接創(chuàng)建一個(gè)有屬性的book對(duì)象。

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)

這樣,我們就測(cè)試了bookshelf的方法。

instance_double 是RSpec 3之后替代double,并支持verifying double,意思是當(dāng)使用stub的方法后,RSpec還會(huì)去驗(yàn)證被double的真實(shí)對(duì)象是否具有這個(gè)方法。

Method Stubs


Stub 既可以模擬假對(duì)象(test double)的方法,也可以模擬真對(duì)象(real object)的方法。RSpec-mock提供下面3種創(chuàng)建method stub的方法。


allow(book).to receive(:title) { "The RSpec Book" } # 第一個(gè)參數(shù)(book)是對(duì)象,第二個(gè)參數(shù)是方法, 第三個(gè)參數(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返回多個(gè)值

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的方法,會(huì)按順序依次返回。

Mock


Mock與Stub的區(qū)別在于, Mock是對(duì)象層面的,Stub是方法層面。Mock對(duì)象同時(shí)支持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 老新語(yǔ)法差異(前為老,后為新),前后的作用基本一樣:

  • stub vs allow
  • should_recevice vs expect().to recevice
  • any_instance (已遺棄)
  • stub_chain (已遺棄)
  • unstub vs and_call_original

1. stub vs allow

對(duì)于should 方法做斷言,通常對(duì)應(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

對(duì)于RSpec3的 expect方法,對(duì)應(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類(lèi)的所有實(shí)例都具備stub的方法,不過(guò)該方法已經(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類(lèi)的實(shí)例都會(huì)有foo方法,當(dāng)使用should_recevice時(shí),所有的Object的實(shí)例都必須響應(yīng)foo方法,否則就會(huì)報(bào)錯(cuò)。

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 是類(lèi)似的功能,使用with可以讓特定的參數(shù)輸入時(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 使用塊修改原有的方法

具體見(jiàn): Configuring responses

References

instance_double and verifying double

rspec-verifying-doubles

rspec mock 2.14

Test double

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容