2012-11-29 57 views

回答

20

這應該是兩個測試。 RSpec best practices call for one assertion per test

describe "#bar" do 
    subject { lambda { Foo.bar } } 

    it { should change { Counter.count }.by 1 } 
    it { should change { AnotherCounter.count }.by 1 } 
end 
+7

有時這會導致大量樣板代碼(當規範需要複雜的設置時)。或者,也許我只是做錯了:) –

+2

一般來說,如果我做了很多樣板,我會嘗試改進我的上下文/描述塊,以便我可以在之前的塊中進行設置。這通常會清理它。 –

+3

對於模型/單元測試來說,這一切都很好,但是關於特性/集成測試呢,在一次測試中做出許多斷言是正常的呢? – Arcolye

2

我忽略了最佳做法的原因有兩個:

  1. 一組我的測試是迴歸測試,我希望他們跑得快,而且 他們打破很少。清晰地確定 破解的優勢並不是很大,而且重構我的代碼 以使其多次運行相同事件的速度放慢對我而言是重要的。
  2. 我有點懶,有時,它更容易這樣做,重構

的方式我這樣做(當我必須這樣做)是依靠事實證明我的數據庫啓動空的,所以我可以接着寫:

foo.bar 
expect(Counter.count).to eq(1) 
expect(Anothercounter.count).to eq(1) 

在某些情況下,我的數據庫不是空的,但我不是計數之前知道,或者我可以明確地測試了之前數:

counter_before = Counter.count 
another_counter_before = Anothercounter.count 

foo.bar 

expect(Counter.count - counter_before).to eq(1) 
expect(Anothercounter.count - another_counter_before).to eq(1) 

最後LY,如果你有大量的對象,檢查(我有時做),你可以這樣做,因爲:

before_counts = {} 
[Counter, Anothercounter].each do |classname| 
    before_counts[classname.name] = classname.count 
end 

foo.bar 

[Counter, Anothercounter].each do |classname| 
    expect(classname.count - before_counts[classname.name]).to be > 0 
end 

如果您有類似的需求對我來說這將工作,我唯一的建議是用做這個你睜着眼睛 - 提出的其他解決方案更加優雅,但在某些情況下有一些缺點。

11

如果您不想使用前面建議的基於速記/上下文的方法,您也可以這樣做,但會被警告它會運行兩次期望值,因此可能不適合所有測試。

it "should increment the count" do 
    expectation = expect { Foo.bar } 
    expectation.to change { Counter.count }.by 1 
    expectation.to change { AnotherCounter.count }.by 1 
end 
+0

謝謝你回答實際問題,非常感謝。 –

+7

嗯,它仍然運行該塊兩次。 –

+0

如果其中一個期望是提出錯誤,這是行不通的 - 它分配期望失敗 – xxjjnn

71

我喜歡這種語法(rspec的3或更高版本):

it "should increment the counters" do 
    expect { Foo.bar }.to change { Counter,  :count }.by(1).and \ 
         change { AnotherCounter, :count }.by(1) 
end 

是的,這是在一個地方兩個斷言,但由於該塊被執行只是一個時間,它可以加速比測試。

編輯:添加反斜槓.and後,以避免語法錯誤

+0

這隻測試最後的斷言,而忽略所有以前的斷言。請參閱我的回答,瞭解合併斷言的正確方法。 – somecto

+1

在Rspec 3中,如果使用組合運算符'.and'(但不能像在原始答案中那樣使用rspec文檔或&&運算符中描述的單個'&'別名),則上述方法將起作用。我編輯了答案,按預期工作。 –

+3

實際上,使用rspec 3的組合結果在塊運行多次:(嘆氣。謝謝貨物Cultists堅持譴責每個人的集成測試,需要更長的數量級運行比他們需要。 –

3

喬治Ladermann的語法是更好,但它不工作。測試多個值更改的方法是通過組合數組中的值。否則,只有最後一次更改斷言纔會決定測試。

這是我如何做到這一點:

it "should increment the counters" do 
    expect { Foo.bar }.to change { [Counter.count, AnotherCounter.count] }.by([1,1]) 
end 

這與 '爲了' 功能工作perfectecly。

+3

不幸的是,這不是在做你認爲的事情。它只是斷言最後的計數是[1,1]。爲了明白我的意思,用一些已經存在的Counter記錄運行你的測試。 –

+0

請參閱[我的答案](http://stackoverflow.com/a/24591809/173542)爲rspec 3工作change_multiple匹配器 –

+0

@MichaelJohnston你說得很對。它不適用'.by'方法,但它可以與'.to'方法一起使用。所以實際上,如果可以預期多個表中的變化,它確實回答了原來的問題。另外,在這種情況下塊只運行一次。我同意將測試限制在一個元素上實在是不理想。 – somecto

2

在所有提出的解決方案都未被證明實際工作之後,我通過添加一個change_multiple匹配器來完成此任務。這將僅適用於3 RSpec的工作,使用的不是2 *

module RSpec 
    module Matchers 
    def change_multiple(receiver=nil, message=nil, &block) 
     BuiltIn::ChangeMultiple.new(receiver, message, &block) 
    end 
    alias_matcher :a_block_changing_multiple, :change_multiple 
    alias_matcher :changing_multiple,   :change_multiple 

    module BuiltIn 
     class ChangeMultiple < Change 
     private 

      def initialize(receiver=nil, message=nil, &block) 
      @change_details = ChangeMultipleDetails.new(receiver, message, &block) 
      end 
     end 
     class ChangeMultipleDetails < ChangeDetails 
     def actual_delta 
      @actual_after = [@actual_after].flatten 
      @actual_before = [@actual_before].flatten 
      @actual_after.map.with_index{|v, i| v - @actual_before[i]} 
     end 
     end 
    end 
    end 
end 

例如:

it "expects multiple changes despite hordes of cargo cultists chanting aphorisms" do 
    a = "." * 4 
    b = "." * 10 
    times_called = 0 
    expect { 
    times_called += 1 
    a += ".." 
    b += "-----" 
    }.to change_multiple{[a.length, b.length]}.by([2,5]) 
    expect(times_called).to eq(1) 
end 

製作的change_multiple by_at_leastby_at_most工作將需要一些額外的工作。

+0

這很好,但是混合斷言是不可能的,例如'x'應該從1改變爲2,'y'應該改變爲3,'z'應該從4改變,並且k應該簡單地改變。 –

4

我發現最好的辦法是做「人工」:

counters_before   = Counter.count 
another_counters_before = AnotherCounter.count 
Foo.bar 
expect(Counter.count).to eq (counters_before + 1) 
expect(AnotherCounter.count).to eq (another_counters_before + 1) 

不是最完美的解決方案,但它的工作原理

21

我試圖用@MichaelJohnston's solution語法錯誤;這是最終爲我工作形式:

it "should increment the counters" do 
    expect { Foo.bar }.to change { Counter.count }.by(1) 
    .and change { AnotherCounter.count }.by(1) 
end 

我要提到我使用紅寶石2.2.2p95 - 我不知道,如果這個版本在解析一些微妙的變化,導致我得到的錯誤,似乎沒有任何其他人在這個線程中有這個問題。

+1

是的。這也適用於我。謝謝。 – poweratom

+0

這裏另一個大拇指。在2016年中期的最新版本中工作。你也可以將params而不是block傳遞給像這樣的'change'方法:'如果這會讓你感到尷尬,請改變(Counter,:count).by(1)'。 –

相關問題