[testing] 模擬和存根之間有什麼區別?


13 Answers

前言

有幾個對象的定義,並不是真實的。 總稱是測試雙 。 這個術語包含: 虛擬的假的存根模擬

參考

根據馬丁福勒的文章

  • 虛擬對像被傳遞但從未實際使用過。 通常它們只是用來填充參數列表。
  • 假的對象實際上有工作的實現,但通常會採取一些快捷方式,使它們不適合生產(內存數據庫就是一個很好的例子)。
  • 存根提供了在測試過程中進行的調用的解答,通常對測試之外的任何事情都沒有響應。 存根還可以記錄關於呼叫的信息,例如記錄其“發送”消息的電子郵件網關存根,或者可能僅記錄“發送”的消息數量。
  • 嘲笑是我們在這裡討論的內容:預先編制了預期的對象,這些預期形成了預期要接收的呼叫的規範。

樣式

Mocks vs Stubs =行為測試與狀態測試

原理

根據每個測試只測試一件事的原則,在一次測試中可能有幾個存根,但通常只有一個模擬。

生命週期

測試與存根的生命週期:

  1. 設置 - 準備正在測試的對象及其存根協作者。
  2. 練習 - 測試功能。
  3. 驗證狀態 - 使用斷言來檢查對象的狀態。
  4. 拆解 - 清理資源。

使用mocks測試生命週期:

  1. 設置數據 - 準備正在測試的對象。
  2. 設置期望 - 準備主對象正在使用的模擬期望。
  3. 練習 - 測試功能。
  4. 驗證期望 - 驗證在模擬中調用了正確的方法。
  5. 驗證狀態 - 使用斷言來檢查對象的狀態。
  6. 拆解 - 清理資源。

概要

模擬和存根測試都給出了這個問題的答案: 結果是什麼?

模擬測試也感興趣: 結果如何實現?

Question

我讀過各種關於嘲笑與測試中殘段的文章,包括Martin Fowler的Mocks Are Stubs ,但仍不明白其中的差別。




這張幻燈片解釋了主要差異非常好。

*來自華盛頓大學CSE 403講座16(幻燈片由“Marty Stepp”創作)




存根是一個空函數,用於在測試期間避免未處理的異常:

function foo(){}

模擬是一種人造功能,用於在測試過程中避免操作系統,環境或硬件依賴性:

function foo(bar){ window = this; return window.toString(bar); }

在斷言和狀態方面:

  • 嘲笑在事件或狀態改變之前被聲明
  • 存根未被斷言,它們在事件之前提供狀態以避免從不相關單元執行代碼
  • 間諜設置像存根,然後在事件或狀態更改後聲明
  • 假貨沒有被斷言,它們在具有硬編碼相關性的事件之後運行以避免狀態

參考




我認為他們之間最重要的區別是他們的意圖。

讓我試著在“ 為什麼存根”與“ 為什麼模擬”中解釋它

假設我正在為我的mac twitter客戶端的公共時間線控制器編寫測試代碼

這裡是測試示例代碼

twitter_api.stub(:public_timeline).and_return(public_timeline_array)
client_ui.should_receive(:insert_timeline_above).with(public_timeline_array)
controller.refresh_public_timeline
  • STUB:Twitter API的網絡連接非常緩慢,這使我的測試變得緩慢。 我知道它會返回時間表,所以我做了一個模擬HTTP twitter API的存根,以便我的測試能夠非常快地運行,並且即使我處於脫機狀態,我也可以運行測試。
  • MOCK:我還沒有寫過任何我的UI方法,我不確定我需要為我的UI對象編寫什麼方法。 我希望通過編寫測試代碼知道我的控制器將如何與我的ui對象進行協作。

通過編寫模擬,您可以通過驗證滿足期望來發現對象協作關係,而存根則只能模擬對象的行為。

我建議閱讀這篇文章,如果你想更多地了解mock: http://jmock.org/oopsla2004.pdf : http://jmock.org/oopsla2004.pdf




如果您將其與調試進行比較:

存根就像確保一個方法返回正確的值

模擬就像實際進入該方法並在返回正確的值之前確保內部的所有內容都是正確的。




存根不會讓你的測試失敗,模擬可以。




我遇到了UncleBob The Little Mocker這篇有趣的文章。 它以一種非常容易理解的方式解釋了所有的術語,所以它對初學者很有用。 Martin Fowlers的文章尤其適合像我這樣的初學者。




我喜歡Roy Osherove提出的解釋[視頻鏈接]

每個創建的類或對像都是假的。 如果您驗證了對它的呼叫,這是一個模擬。 否則它是一個存根。




  • 存根對嘲笑
    • 存根
      • 為方法調用提供特定的答案
      • 被測試代碼使用它來隔離它
      • 不能失敗測試
      • 經常實施抽象方法
    • 嘲弄
      • 存根的“超集”; 可以斷言某些方法被調用
      • 用於測試被測代碼的行為
      • 可能會失敗測試
      • 經常嘲笑接口



我在回答中使用了python示例來說明差異。

存根 - 存根是一種軟件開發技術,用於在開發生命週期的早期階段實現類的方法。 它們通常用作實現已知接口的佔位符,接口已定型或已知,但實現尚未知道或最終完成。 你從存根開始,這僅僅意味著你只寫下一個函數的定義,並留下實際的代碼以備後用。 優點是你不會忘記方法,你可以在代碼中看到它的同時繼續思考你的設計。 您也可以讓存根返回一個靜態響應,以便響應可以立即用於代碼的其他部分。 存根對象提供了有效的響應,但無論您傳入什麼輸入,它都是靜態的,您始終會得到相同的響應:

class Foo(object):
    def bar1(self):
        pass

    def bar2(self):
        #or ...
        raise NotImplementedError

    def bar3(self):
        #or return dummy data
        return "Dummy Data"

模擬對像用於模擬測試用例,它們驗證在這些對像上調用某些方法。 模擬對像是模擬對象,以受控方式模擬真實對象的行為。 您通常會創建一個模擬對象來測試其他對象的行為。 Mock讓我們模擬單元測試無法使用或過於笨拙的資源。

mymodule.py:

import os
import os.path

def rm(filename):
    if os.path.isfile(filename):
        os.remove(filename)

test.py:

from mymodule import rm
import mock
import unittest

class RmTestCase(unittest.TestCase):
    @mock.patch('mymodule.os')
    def test_rm(self, mock_os):
        rm("any path")
        # test that rm called os.remove with the right parameters
        mock_os.remove.assert_called_with("any path")

if __name__ == '__main__':
    unittest.main()

這是一個非常基本的例子,它運行rm並聲明它被調用的參數。 你可以像這裡所示的那樣使用模擬對象而不僅僅是函數,並且你也可以返回一個值,這樣一個模擬對象可以被用來代替一個存根來進行測試。

關於unittest.mock更多信息,請注意python 2.x mock不包含在unittest中,而是一個可以通過pip(pip install mock)下載的可下載模塊。

我還讀過Roy Osherove的“單元測試的藝術”,我認為如果用Python和Python編寫一個類似的書,這將會很棒。 如果有人知道這樣的書,請分享。 乾杯:)




從jMock的開發者的論文“ 模擬角色”,而不是“對象”

存根是生產代碼的虛擬實現,返回預設結果。 模擬對象充當存根,但也包括斷言來衡量目標對象與其鄰居的相互作用。

所以,主要區別是:

  • 在存根上設置的期望通常是通用的,而在模擬上設置的期望可以更“聰明”(例如,在第一次調用時返回該值,在第二次調用時返回等等)。
  • 存根主要用於設置SUT的間接輸入 ,而模擬可用於測試SUT的間接輸入和間接輸出。

總而言之,同時也試圖分散福勒的文章標題中的混淆: 嘲笑是存根,但它們不僅是存根







以下是對每一個跟隨現實世界樣本的描述。

  • 虛擬 - 只是虛假的價值觀,以滿足API

    示例 :如果您正在測試一個類的方法,該類需要構造函數中的許多必需參數,而這些參數對您的測試沒有任何影響 ,則可以創建虛擬對像以創建類的新實例。

  • - 創建一個可能依賴某些外部基礎結構的類的測試實現。 (您的單元測試實際上不會與外部基礎架構進行實際交互,這是很好的做法。)

    示例 :為訪問數據庫創建假實現,將其替換in-memory集合。

  • 存根 - 覆蓋方法返回硬編碼值,也稱為state-based

    示例 :您的測試類取決於方法Calculate()需要5分鐘才能完成。 而不是等待5分鐘,您可以用存根返回硬編碼值來替換它的實際實現; 只佔一小部分時間。

  • 模擬 - 與Stub非常相似,但interaction-based而不是基於狀態。 這意味著您不希望Mock返回某個值,而是假定進行特定的方法調用順序。

    示例:您正在測試用戶註冊類。 調用Save ,它應該調用SendConfirmationEmail

StubsMock實際上是Mock子類型,它們都將真正的實現與測試實現交換,但是出於不同的具體原因。




存根用於您在測試中設置的具有預期返回值的方法。 Mocks用於void方法,在Assert中驗證它們被調用。




Related