testing mock教學 - 模擬和存根之間有什麼區別?




stub double (25)

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

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

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

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


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


要非常清楚和實用:

存根(stub):實現類/對象的方法的類或對象,被偽造並始終返回您想要的。

JavaScript中的示例:

var Stub = {
   method_a: function(param_a, param_b){
      return 'This is an static result';
   }
}

模擬:存根的相同,但它增加了一些邏輯,“驗證”何時調用方法,所以你可以確定一些實現正在調用該方法。

正如@mLevan所說,想像一下你正在測試一個用戶註冊類的例子。 調用Save之後,它應該調用SendConfirmationEmail。

一個非常愚蠢的代碼示例:

var Mock = {
   calls: {
      method_a: 0
   }

   method_a: function(param_a, param_b){
     this.method_a++; 
     console.log('Mock.method_a its been called!');
   }
}

以下是我的理解...

  • 如果您在本地創建測試對象並使用它提供本地服務,那麼您正在使用模擬對象。 這將對您在本地服務中實施的方法進行測試。 它用於驗證行為

  • 當你從實際服務提供者那裡獲得測試數據時,儘管從測試版本的接口獲得測試版本的對象,但你正在使用存根,存根可以有邏輯接受某些輸入並給出相應的輸出來幫助你執行狀態驗證...



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

function foo(){}

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

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

在斷言和狀態方面:

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

參考


前言

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

參考

根據馬丁福勒的文章

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

樣式

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

原理

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

生命週期

測試與存根的生命週期:

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

使用mocks測試生命週期:

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

概要

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

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


存根是簡單的假物體。 它只是確保測試順利進行。
模擬是更聰明的存根。 您驗證您的測試通過它。


Stub是實現組件接口的對象,但不是返回組件在調用時返回的內容,而是可以將存根配置為返回適合測試的值。 使用存根單元測試可以測試單元是否可以處理來自其協作者的各種返回值。 在單元測試中使用存根而不是真正的協作者可以這樣表達:

單元測試 - >存根

單元測試 - >單元 - >存根

單元測試聲明單元的結果和狀態

首先,單元測試會創建存根並配置其返回值。 然後單元測試創建單元並在其上設置存根。 現在單元測試調用單元,然後調用存根。 最後,單元測試對單元上的方法調用的結果做出斷言。

Mock 就像一個存根,只有它也有方法可以決定在Mock上調用哪些方法 。 因此,使用模擬可以測試單元是否可以正確處理各種返回值,並且如果單元正確使用協作器。 例如,通過使用Statement或PreparedStatement從數據庫中讀取數據,您無法看到由dao對象返回的值。 在返回值之前,您也不能看到connection.close()方法是否被調用。 這是可能的嘲笑。 換句話說,模擬可以測試單位與協作者的完整交互。 不僅僅是返回單元使用的值的協作者方法。 在單元測試中使用模擬可以表達如下:

單元測試 - >模擬

單元測試 - >單元 - >模擬

單元測試聲明單元的結果和狀態

單元測試在mock上調用的方法上聲明

更多詳情>> Here


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

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

所以,主要區別是:

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

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


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

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


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


模擬 - 模擬攔截對方法或函數的調用(或者一組方法和函數,就像模擬類一樣)。 它不是該方法或功能的替代方案。 在這個攔截過程中,模擬可以做任何想要的事情,例如記錄輸入和輸出,決定短路呼叫,改變返回值等。

存根 - 存根是方法或函數(或一組方法和函數,就像存根類一樣)的有效完整工作實現,它具有與方法,函數或方法和函數組相同的接口/簽名正在為此而st for。 通常,stubbed實現只會在單元測試的上下文中執行可接受的事情,這意味著它不會在執行IO的同時模仿它所存儲事物的行為。


存根幫助我們運行測試。 怎麼樣? 它提供了有助於運行測試的值。 這些值本身並不是真實的,我們創建這些值只是為了運行測試。 例如,我們創建一個HashMap來為我們提供與數據庫表中的值類似的值。 所以不是直接與數據庫交互,而是與Hashmap交互。

模擬是一個運行測試的假對象。 我們放置斷言。


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

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

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


我認為關於這個問題的最簡單和最清晰的答案來自Roy Osherove在他的“ 單元測試藝術” (第85頁)

告訴我們處理存根的最簡單方法是注意存根永遠不會失敗。 斷言測試使用總是針對被測試的類。

另一方面,測試將使用模擬對象來驗證測試是否失敗。 [...]

再次,模擬對像是我們用來查看測試是否失敗的對象。

這意味著,如果您對虛假進行斷言,則意味著您使用虛假作為模擬,如果您僅使用虛假來運行測試而沒有對其進行斷言,那麼您將使用虛假作為存根。


閱讀以上所有的解釋,讓我試著濃縮:

  • 存根 :可以讓測試運行的一段代碼,但不關心它會發生什麼。
  • 模擬 :一段虛擬代碼,你可以在測試中正確調用VERIFY。

存根和模擬測試的觀點:

  • 存根是由用戶以靜態方式完成的虛擬實現,即在Stub中編寫實現代碼。 所以它不能處理服務定義和動態條件,通常這是在沒有使用模擬框架的JUnit框架中完成的。

  • 模擬也是虛擬的實現,但它的實現通過使用Mockito等Mocking框架以動態的方式完成。 所以我們可以以動態方式處理條件和服務定義,例如,可以在運行時從代碼動態創建mock。 所以使用模擬我們可以動態地實現存根。


我在回答中使用了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編寫一個類似的書,這將會很棒。 如果有人知道這樣的書,請分享。 乾杯:)


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

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

假設我正在為我的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


codeschool.com課程, Rails殭屍測試中 ,他們給出了這些術語的定義:

存根

用代碼返回指定結果的方法。

嘲笑

存在斷言的方法被調用的存根。

正如Sean Copenhaver在他的回答中所描述的那樣,區別在於mock設定了期望值(即做出斷言,關於是否或如何調用)。


存根

我相信最大的區別是你已經用預定的行為寫了一個存根。 所以你會有一個類來實現你為了測試目的而偽裝的依賴項(最有可能的抽像類或接口),並且這些方法只會被設置為響應而被刪除。 他們不會做任何事情,而且你已經在測試之外編寫了它的代碼。

嘲笑

模擬是你測試的一部分,你必須設置你的期望。 模擬不是以預定的方式設置的,所以你有代碼在你的測試中完成。 嘲笑是在運行時確定的,因為設置預期的代碼在執行任何操作之前必須運行。

區別

使用mock編寫的測試通常遵循initialize -> set expectations -> exercise -> verify模式進行測試。 雖然預先寫好的存根會遵循initialize -> exercise -> verify

相似

兩者的目的是消除測試類或函數的所有依賴關係,以便讓您的測試更加關注並簡化他們試圖證明的內容。


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

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

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

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

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

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

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

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

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

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






testing mocking stub