RSpec, Test Double, Mock, and Stub

Rspec是ruby的測試框架之一予跌。

Mockstub都屬于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 使用塊修改原有的方法

具體見: Configuring responses

References

instance_double and verifying double

rspec-verifying-doubles

rspec mock 2.14

Test double

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市叮称,隨后出現(xiàn)的幾起案子捎废,更是在濱河造成了極大的恐慌,老刑警劉巖致燥,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件登疗,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)辐益,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門断傲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人智政,你說我怎么就攤上這事认罩。” “怎么了续捂?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵垦垂,是天一觀的道長。 經(jīng)常有香客問我牙瓢,道長劫拗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任矾克,我火速辦了婚禮页慷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胁附。我一直安慰自己酒繁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布控妻。 她就那樣靜靜地躺著州袒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饼暑。 梳的紋絲不亂的頭發(fā)上稳析,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天,我揣著相機(jī)與錄音弓叛,去河邊找鬼彰居。 笑死,一個胖子當(dāng)著我的面吹牛撰筷,可吹牛的內(nèi)容都是我干的陈惰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼毕籽,長吁一口氣:“原來是場噩夢啊……” “哼抬闯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起关筒,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤溶握,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蒸播,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體睡榆,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡萍肆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了胀屿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塘揣。...
    茶點(diǎn)故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖宿崭,靈堂內(nèi)的尸體忽然破棺而出亲铡,到底是詐尸還是另有隱情,我是刑警寧澤葡兑,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布奖蔓,位于F島的核電站,受9級特大地震影響铁孵,放射性物質(zhì)發(fā)生泄漏锭硼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一蜕劝、第九天 我趴在偏房一處隱蔽的房頂上張望檀头。 院中可真熱鬧,春花似錦岖沛、人聲如沸暑始。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽廊镜。三九已至,卻和暖如春唉俗,著一層夾襖步出監(jiān)牢的瞬間嗤朴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工虫溜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雹姊,地道東北人。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓衡楞,卻偏偏與公主長得像吱雏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瘾境,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內(nèi)容