[英]When and when not to stub/mock a test
我正在齊心協力圍繞Rspec努力,以朝着更多的TDD / BDD開發模式邁進。 但是,我還有很長一段路要走,並且在一些基本原則上苦苦掙扎:
就像,什么時候應該使用模擬/存根,什么時候不應該使用?
以這種情況為例:我有一個has_many :blogs
的Site
模型,而Blog
模型has_many :articles
。 在我的Site
模型中,我有一個回調過濾器,該過濾器為每個新網站創建默認的博客和文章集。 我想測試該代碼,所以去了:
describe Site, "when created" do
include SiteSpecHelper
before(:each) do
@site = Site.create valid_site_attributes
end
it "should have 2 blogs" do
@site.should have(2).blogs
end
it "should have 1 main blog article" do
@site.blogs.find_by_slug("main").should have(1).articles
end
it "should have 2 secondary blog articles" do
@site.blogs.find_by_slug("secondary").should have(2).articles
end
end
現在,如果我運行該測試,一切都會通過。 但是,它也很慢,因為它為每個測試都創建了一個新站點,兩個新Blog和三個新文章! 所以我想知道,這是使用存根的好候選人嗎? 讓我們開始吧:
describe Site, "when created" do
include SiteSpecHelper
before(:each) do
site = Site.new
@blog = Blog.new
@article = Article.new
Site.stub!(:create).and_return(site)
Blog.stub!(:create).and_return(@blog)
Article.stub!(:create).and_return(@article)
@site = Site.create valid_site_attributes
end
it "should have 2 blogs" do
@site.stub!(:blogs).and_return([@blog, @blog])
@site.should have(2).blogs
end
it "should have 1 main blog article" do
@blog.stub!(:articles).and_return([@article])
@site.stub_chain(:blogs, :find_by_slug).with("main").and_return(@blog)
@site.blogs.find_by_slug("main").should have(1).articles
end
it "should have 2 secondary blog articles" do
@blog.stub!(:articles).and_return([@article, @article])
@site.stub_chain(:blogs, :find_by_slug).with("secondary").and_return(@blog)
@site.blogs.find_by_slug("secondary").should have(2).articles
end
end
現在,所有測試仍然通過,而且速度也有所提高。 但是,我的測試時間增加了一倍,而整個練習卻毫無意義,因為我不再測試代碼,而只是測試了。
現在,或者我完全錯過了模擬/存根的要點,或者我從根本上解決了這個問題,但是我希望有人能夠做到:
但是,我的測試時間增加了一倍,而整個練習卻毫無意義,因為我不再測試代碼,而只是測試了。
這就是這里的關鍵。 不測試代碼的測試沒有用。 如果您可以否定地更改您應該測試的代碼,並且測試不會失敗,則不值得擁有。
根據經驗,除非有必要,否則我不喜歡模擬/存根任何內容。 例如,當我編寫控制器測試時,我想確保在記錄保存失敗時發生適當的動作,我發現將對象的save
方法存根以返回false更容易,而不是僅僅精心設計參數因此,為了確保模型無法保存。
另一個示例是一個名為admin?
的助手admin?
會根據當前登錄的用戶是否是管理員來返回true或false。 我不想偽造用戶登錄,所以我這樣做了:
# helper
def admin?
unless current_user.nil?
return current_user.is_admin?
else
return false
end
end
# spec
describe "#admin?" do
it "should return false if no user is logged in" do
stubs(:current_user).returns(nil)
admin?.should be_false
end
it "should return false if the current user is not an admin" do
stubs(:current_user).returns(mock(:is_admin? => false))
admin?.should be_false
end
it "should return true if the current user is an admin" do
stubs(:current_user).returns(mock(:is_admin? => true))
admin?.should be_true
end
end
作為中間立場,您可能需要研究Shoulda 。 這樣,您只需確保模型已定義了關聯,並相信Rails已經過充分的測試,因此關聯將“正常工作”,而無需創建關聯模型然后對其進行計數。
我有一個名為Member
的模型,基本上我的應用程序中的所有內容都與之相關。 它定義了10個關聯。 我可以測試每個關聯,也可以這樣做:
it { should have_many(:achievements).through(:completed_achievements) }
it { should have_many(:attendees).dependent(:destroy) }
it { should have_many(:completed_achievements).dependent(:destroy) }
it { should have_many(:loots).dependent(:nullify) }
it { should have_one(:last_loot) }
it { should have_many(:punishments).dependent(:destroy) }
it { should have_many(:raids).through(:attendees) }
it { should belong_to(:rank) }
it { should belong_to(:user) }
it { should have_many(:wishlists).dependent(:destroy) }
我不太同意其他人。 真正的問題(如我所見)是,您正在使用相同的測試(發現行為和創建)測試多個有趣的行為。 有關導致此問題的原因的詳細信息,請參見此討論: http : //www.infoq.com/presentations/integration-tests-scam 。 對於此答案的其余部分,我假設您要測試創建的內容就是您要測試的內容。
隔離測試似乎很笨拙,但這通常是因為它們有設計課程來教您。 以下是我可以從中看到的一些基本內容(盡管沒有看到生產代碼,但是我做不到太多的事情)。
首先,要查詢設計,讓Site
向博客添加文章是否有意義? Blog
上的類方法Blog.with_one_article
樣Blog.with_one_article
然后,這意味着您需要測試的是該類方法已被調用兩次(如果[據我目前所知],則每個Site
都有一個“主” Blog
和“輔助” Blog
,並且已經建立了關聯(我還沒有找到在Rails中執行此操作的好方法,我通常不對其進行測試)。
此外,您在調用Site.create
時是否覆蓋ActiveRecord的create方法? 如果是這樣,我建議在Site上創建一個新的類方法,命名為其他名稱(可能是Site.with_default_blogs
?)。 這只是我的一個普遍習慣,過多的東西通常會在以后的項目中引起問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.