2016-05-12 28 views
4

我似乎遇到過文獻暗指使用RSpec的any_instance_of方法(例如expect_any_instance_of)是不好的做法。津津有味的文檔甚至在「使用遺留代碼」一節(http://www.relishapp.com/rspec/rspec-mocks/v/3-4/docs/working-with-legacy-code/any-instance)下列出了這些方法,這意味着我不應該利用這個編寫新代碼。測試在新實例上調用方法的函數的「正確」方法是什麼?

我覺得我經常編寫依賴此功能的新規格。一個很好的例子是創建一個新實例然後調用一個方法的任何方法。 (在Rails這裏爲MyModel是一個ActiveRecord)我經常寫做類似下面的方法:

def my_method 
    my_active_record_model = MyModel.create(my_param: my_val) 
    my_active_record_model.do_something_productive 
end 

我一般寫我的規格尋找do_something_productive被稱爲與使用expect_any_instance_of的。例如:

expect_any_instance_of(MyModel).to receive(:do_something_productive) 
subject.my_method 

唯一的其他辦法,我可以看到符合規範,這將是有這樣一羣存根:

my_double = double('my_model') 
expect(MyModel).to receive(:create).and_return(my_double) 
expect(my_double).to receive(:do_something_productive) 
subject.my_method 

不過,我認爲這更糟糕的是因爲)它的長,慢寫,和b)它比第一種方式更加脆弱和白色。爲了說明第二點,如果我改變my_method以下幾點:

def my_method 
    my_active_record_model = MyModel.new(my_param: my_val) 
    my_active_record_model.save 
    my_active_record_model.do_something_productive 
end 

那麼規範斷裂的雙版本,但any_instance_of版本的作品就好了。

所以我的問題是,其他開發人員如何做到這一點?我的方法使用any_instance_of皺眉了嗎?如果是這樣,爲什麼?

回答

1

我沒有比這兩個測試代碼更好的解決方案。在stubbing/mocking解決方案中,我會使用allow而不是expect來撥打create,因爲create呼叫不是規範的要點,但這是一個側面問題。我同意,殘留和嘲諷是痛苦的,但這通常是我所做的。

但是,該代碼只有一點功能羨慕。提取方法到MyModel清除了異味並消除了測試問題:

class MyModel < ActiveRecord::Base 
    def self.create_productively(attrs) 
    create(attrs).do_something_productive 
    end 
end 

def my_method 
    MyModel.create_productively(attrs) 
end 

# in the spec 
expect(MyModel).to receive(:create_productively) 
subject.my_method 

create_productively是一個模型的方法,所以它可以而且應該與實際情況進行測試,而且也沒有必要存根或嘲笑。

我經常注意到需要使用不太常用的RSpec特性,這意味着我的代碼可能會使用一些重構。

+0

你現在在'create_productively'方法中有沒有完全相同的問題? –

+0

如果除了在模型上創建和調用方法之外,主題還有更多的功能,這將會更加明顯。但無論如何看到我的更新。 –

+0

我的意思是,你現在如何測試'create_productively'。我看不出這有何幫助。也許我只是想念它。 –

1

這是怎樣的一個咆哮的,但這裏是我的想法:

所津津樂道的文檔,甚至列出了「遺留代碼」一節下,這些方法(http://www.relishapp.com/rspec/rspec-mocks/v/3-4/docs/working-with-legacy-code/any-instance),這意味着我不應該寫利用此新代碼。

我不同意這一點。嘲諷/剔除是有效使用時的寶貴工具,應該與斷言風格測試一起使用。其原因是,模擬/存根可實現「外置式」測試方法,您可以最大限度地減少耦合並測試高級功能,而無需調用堆棧中的每個小數據庫事務,API調用或輔助方法。

問題的確是你想測試狀態還是行爲?顯然,你的應用程序涉及到兩個方面,因此將自己束縛於單一測試範例是沒有意義的。通過斷言/期望的傳統測試對於測試狀態是有效的,並且很少涉及狀態如何改變。另一方面,嘲笑迫使你考慮對象之間的接口和交互,減少狀態自身的突變負擔,因爲你可以存根和填充返回值等。 但是,當使用*_any_instance_of時,我會提醒注意並避免如果可能的話。這是一個非常鈍的工具,可能很容易被濫用,特別是當項目規模較小時,只有在項目規模較大時纔會成爲負債。我通常將*_any_instance_of作爲一種氣味,無論是我的代碼還是測試,都可以改進,但有時候需要使用。

話雖這麼說,你提出了兩種方法之間,我更喜歡這一個:

my_double = double('my_model') 
expect(MyModel).to receive(:create).and_return(my_double) 
expect(my_double).to receive(:do_something_productive) 
subject.my_method 

這是明確的,以及隔離,不與數據庫調用招致開銷。如果my_method的實施更改,它可能需要重寫,但沒關係。由於它的隔離度很好,如果my_method以外的任何代碼發生變化,它可能不需要重寫。將其與在數據庫中刪除列的斷言幾乎可以打破整個測試套件的斷言進行對比。

0
def self.my_method(attrs) 
    create(attrs).tap {|m| m.do_something_productive} 
end 

# Spec 
let(:attrs) { # valid hash } 

describe "when calling my_method with valid attributes" do 
    it "does something productive" do 
    expect(MyModel.my_method(attrs)).to have_done_something_productive 
    end 
end 

當然,您將有其他測試#do_something_productive本身。

權衡總是相同的:嘲諷和存根都很快但很脆弱。真實物體較慢但較脆弱,通常需要較少的測試維護。

我傾向於爲外部依賴性(例如API調用)保留模擬/存根,或者使用已定義但未實現的接口時。

相關問題