2008-09-06 36 views
14

前段時間我看了Mocks Aren't Stubs文章由Martin Fowler,我必須承認我有點害怕,關於增加了複雜的外部依賴,所以我想問一下:嘲笑比存根更好嗎?

是什麼時所使用的最佳方法單元測試?

總是使用模擬框架自動模擬正在測試的方法的依賴關係還是更喜歡使用更簡單的機制(如實例測試存根)更好?

回答

12

正如口頭禪說的那樣,'用最簡單的東西去做可能的工作。'

  1. 如果假班可以完成工作,請和他們一起去。
  2. 如果您需要一個具有多個方法的接口進行模擬,請使用模擬框架。

避免使用嘲笑總是因爲他們使測試脆。如果模擬接口或實現發生更改......測試中斷,那麼現在您的測試對實現所調用的方法有着複雜的瞭解。這很糟糕,你會花費更多的時間讓你的測試運行,而不是讓你的SUT運行。 測試不應該與實施不恰當地密切相關。
所以用你最好的判斷..我更喜歡mock,它會幫助我節省寫作 - 用n >> 3方法更新假類。

更新尾聲/商議:
(感謝Toran比盧普斯例如mockist測試見下文。)
喜道格,那麼我認爲我們已經超越了成另一種神聖的戰爭 - 經典TDDers VS Mockist TDDers 。我想我屬於前者。

  • 如果我在測試#101 Test_ExportProductList,我覺得我需要一個新的PARAM添加到IProductService.GetProducts()。我這樣做得到這個測試綠色。我使用重構工具來更新所有其他參考。現在我發現所有調用這個成員的模擬測試現在都爆炸了。然後我必須回去更新所有這些測試 - 浪費時間。爲什麼ShouldPopulateProductsListOnViewLoadWhenPostBackIsFalse失敗?是否因爲代碼被破壞?而是測試被打破。我贊成一個測試失敗= 1個地方來修復。嘲笑頻率與此相反。存根會更好嗎?如果我有一個fake_class.GetProducts()..確定一個地方改變,而不是多次Expect調用霰彈槍手術。最後,這是一個風格問題..如果你有一個常用的實用方法MockHelper.SetupExpectForGetProducts() - 這也足夠了..但你會發現這是不常見的。
  • 如果您在測試名稱上放置白色條,測試很難閱讀。模擬框架的許多管道代碼隱藏了正在執行的實際測試。
  • 要求您瞭解嘲弄的框架的這種特殊的味道
1

閱讀Luke Kanies關於this blog post中這個問題的討論。他引用了a post from Jay Fields,這甚至表明使用[相當於ruby's/mocha's] stub_everything可以使測試更健壯。引用Fields的最後一句話:「Mocha使得定義一個模擬和定義一個存根一樣容易,但這並不意味着你應該總是喜歡mock。事實上,我通常更喜歡存根,並在必要時使用mock。 「

2

這只是取決於你在做什麼類型的測試。如果你正在做基於行爲的測試,你可能需要一個動態的模擬,以便你可以驗證與你的依賴發生了一些交互。但如果你正在做基於狀態的測試,你可能想要一個存根,所以你驗證值/ etc

例如,在下面的測試中,你注意到我將視圖截掉,所以我可以驗證屬性值設置(基於狀態測試)。然後我創建一個服務類的動態模擬,這樣我就可以確保在測試過程中調用特定的方法(基於交互/行爲的測試)。

<TestMethod()> _ 
Public Sub Should_Populate_Products_List_OnViewLoad_When_PostBack_Is_False() 
    mMockery = New MockRepository() 
    mView = DirectCast(mMockery.Stub(Of IProductView)(), IProductView) 
    mProductService = DirectCast(mMockery.DynamicMock(Of IProductService)(), IProductService) 
    mPresenter = New ProductPresenter(mView, mProductService) 
    Dim ProductList As New List(Of Product)() 
    ProductList.Add(New Product()) 
    Using mMockery.Record() 
     SetupResult.For(mView.PageIsPostBack).Return(False) 
     Expect.Call(mProductService.GetProducts()).Return(ProductList).Repeat.Once() 
    End Using 
    Using mMockery.Playback() 
     mPresenter.OnViewLoad() 
    End Using 
    'Verify that we hit the service dependency during the method when postback is false 
    Assert.AreEqual(1, mView.Products.Count) 
    mMockery.VerifyAll() 
End Sub 
2

最好使用組合,你必須使用自己的判斷。這裏是我使用的指導原則:

  • 如果對外部代碼的調用是代碼的預期行爲(外部)的一部分,則應該對其進行測試。使用模擬。
  • 如果這個調用真的是外界不關心的實現細節,那麼更喜歡存根。但是:
  • 如果您擔心測試代碼的後續實現可能會意外繞過存根,並且您希望注意是否發生這種情況,請使用模擬。您將測試與您的代碼結合起來,但是爲了注意您的存根已不夠用,並且您的測試需要重新工作。

第二種模擬是一種必要的邪惡。真的,這裏發生的事情是,無論您使用存根還是模擬,在某些情況下,您必須將代碼添加到您的代碼中,而不是您想要的。當發生這種情況時,最好使用模擬而不是存根,因爲你會知道何時該耦合斷裂,並且你的代碼不再像測試認爲的那樣書寫。當你這樣做的時候,最好在你的測試中留下評論,這樣任何違反它的人都知道他們的代碼沒有錯,測試是。

而且,這是一種代碼味道和最後的手段。如果您發現需要經常這樣做,請嘗試重新考慮您編寫測試的方式。

7

我通常更喜歡使用模擬,因爲期望。當你在一個存根上調用一個返回值的方法時,它通常會給你一個值。但是當你在模擬上調用某個方法時,它不僅會返回一個值,而且還會強制您設置該方法甚至是首先調用該方法的期望。換句話說,如果你設置了一個期望然後不調用該方法,就會拋出一個異常。當你設定一個期望值時,你基本上會說「如果這個方法沒有被調用,出了點問題。」而事實恰恰相反,如果你在模擬中調用方法而沒有設定期望值,它會拋出一個異常,實質上是說「嘿,當你不期待它時,你在調用這個方法時,你在做什麼」。

有時你不希望對你調用的每個方法都有期望,所以有些模擬框架會允許像模擬/存根混合的「部分」模擬,因爲只有你設定的期望被強制執行,而其他模式方法調用更像是一個存根,它只是返回一個值。

但是我可以想到的一個有效的使用存根的地方就是當您將測試引入遺留代碼時。有時候,通過繼承你正在測試的類來創建一個存根,而不是重構所有的東西,使嘲笑變得簡單甚至可能。

對此...

避免使用嘲笑總是因爲他們做的測試脆。如果模擬界面發生變化......您的測試中斷了,那麼現在您的測試對實現所調用的方法有着複雜的瞭解。所以用你最好的判斷.. <

......我說如果我的界面改變,我的測試更好的休息。因爲單元測試的重點在於他們現在正確地測試我的代碼。

2

沒關係統計與交互。思考角色和關係。如果一個對象與鄰居合作完成其工作,那麼這個關係(如界面中所表達的)就是使用mock進行測試的候選人。如果一個對象是一個具有一些行爲的簡單的值對象,那麼直接進行測試。我看不出手工編寫模擬(甚至是存根)的要點。我們都是這樣開始和重構的。

如需進一步探討,請考慮查看http://www.mockobjects.com/book