一文讓你掌握單元測試的Mock、Stub和Fake

這是我參與8月更文挑戰的第6天,活動詳情查看:8月更文挑戰web

  • 📢歡迎點贊 :👍 收藏 ⭐留言 📝 若有錯誤敬請指正,賜人玫瑰,手留餘香!
  • 📢本文做者:由webmote 原創,首發於 【CSDN】
  • 📢做者格言: 生活在於折騰,當你不折騰生活時,生活就開始折騰你,讓咱們一塊兒加油!💪💪💪

🎏 序言

單元測試中有幾個神祕的概念,它們就是Mock,模擬對象;Stub,存根;Fake,僞對象,它們聽起來很相似,也很容易混淆,讓咱們經過這篇文章揭開它們神祕的面紗,探索其幽深的小徑。算法

🎏 1.什麼是僞對象(Fake)

僞對象,通俗的將就是假貨數據庫

在這裏插入圖片描述 是用來代替具備「智能」對象的假貨實現。一般是一個快捷實現,使它在不一樣的單元測試中有用,但不能用做集成測試。api

到目前爲止,我看到的最多見的例子是數據倉儲層中。假設我有一個標準的 SQL Server 倉儲庫,以下所示:markdown

public interface IUserRepository
{
    void Insert(object user);
    List<object> GetAllUsers();
}

public class UserRepository : IUserRepository
{
    public List<object> GetAllUsers()
    {
        //到數據庫取用戶集. 
    }

    public void Insert(object user)
    {
        //插入用戶到數據庫
    }
}
複製代碼

涉及到實際的實現部分時,可能包含邏輯和調用數據庫的方法。框架

當涉及到對可能使用 IUserRepository 的類(例如 UserService)進行單元測試時,咱們會遇到一些問題。由於咱們不但願咱們的單元測試接觸到數據庫,坦率地說,咱們並不真正關心 UserRepository 的實現。post

因此咱們建立了一個僞對象,而不是直接使用已經實現的真實對象:單元測試

public class FakeUserRepository : IUserRepository
{
    private List<object> _users = new List<object>();

    public List<object> GetAllUsers()
    {
        return _users;
    }

    public void Insert(object user)
    {
        _users.Add(user);
    }
}
複製代碼

在咱們的 fake 中,咱們實際上獲取了插入的用戶,並將其添加到內部列表中。當調用 GetAllUsers 時,咱們返回相同的列表。如今,每當單元測試須要調用 IUserRepository 時,咱們能夠在 FakeUserRepository 中進行補充,而且當即「工做」。測試

這裏的主要內容是實現了業務上的類似!ui

這是一個「真正的」實現,實際上就像一個存儲庫同樣,只是在幕後沒有實際的數據庫。

🎏 2.什麼是存根(Stub)

存根是一種返回硬編碼響應的實現. 在這裏插入圖片描述

存根沒有任何「智能」。沒有將對象上的調用捆綁在一塊兒,而是每一個方法只返回一個預約義的固定響應。

讓咱們看看如何爲上述建立存根:

public class StubOneUserRepository : IUserRepository
{
    public List<object> GetAllUsers()
    {
        return new List<object>();
    }

    public void Insert(object user)
    {
        //啥都不作~
    }
}
複製代碼

看起來它有點相似於咱們的僞對象,但……不徹底是。

這裏插入不影響 GetAllUsers,GetAllUsers 自己返回一個沒有任何內容的預設響應。我在測試期間對這個對象所作的任何事情都不會改變它的功能。

存根用於知足代碼內部的條件,而不是測試功能。

若是個人代碼在存儲庫上調用「插入」,但我並不真正關心個人特定測試的數據會發生什麼,那麼存根是有意義的,這就省去了編寫僞對象「智能」業務的工做。

倉儲庫的例子顯得有些奇葩,由於倉儲庫老是應該返回動態數據來測試代碼中的各類條件。所以,讓我使用另外一個在現實世界中更有可能須要存根的示例。

假設有一個界面告訴用戶是否通過「身份驗證」。它看起來像這樣:

public interface IUserAuthenticatedCheck
{
    bool IsUserAuthenticated();
}
複製代碼

如今對於咱們的測試,老是須要對用戶進行身份驗證,也許是爲了知足一些基礎框架條件。能夠像這樣定義存根:

public class StubUserAuthenticatedCheckTrue : IUserAuthenticatedCheck
{
  //返回驗證過~~~
    public bool IsUserAuthenticated() => true;
}
複製代碼

沒有是否應該對用戶進行身份驗證的智能算法,沒有其餘值,只是一個直接的「老是返回真」的方法。

固定,就是存根擅長的地方。

🎏 3.什麼是模擬對象(Mock)

模擬是一個預設的對象,能夠將動態響應/行爲定義爲測試的一部分,並預先定義好。 在這裏插入圖片描述

它們不須要去特別實現或實例化,而且(一般)不須要在測試之間共享行爲。

咱們將在哪裏使用 Mock 呢?是您想要相對動態的任何地方,對於特定測試知足條件。

假設我正在編寫一個調用如下接口的測試:

public interface IShopService
{
    bool CheckShopIsOpen(int shopId);
}
複製代碼

咱們所作的就是檢查商店是開仍是關。這個實際實現類可能會調用數據庫或某種 webservice/api,但咱們不想將其做爲單元測試的一部分。

若是在這裏使用Fake僞對象,咱們須要添加一些虛擬方法來判斷商店是應該開仍是關。也許是這樣的:

public class FakeShopService : IShopService
{
    public bool ShouldShopBeOpen { get; set; }

    public bool CheckShopIsOpen(int shopId)
    {
        return ShouldShopBeOpen;
    }
}
複製代碼

呃,好像有些複雜,爲了可以控制商店是開放仍是關閉,咱們須要添加新方法。

若是使用存根Stub,必須將真/假響應硬編碼到具體類中。多是這樣的:

public class StubShopService : IShopService
{
    private Dictionary<int, bool> _shops = new Dictionary<int, bool>
    {
        { 1, true },
        { 2, false }
    };

    public bool CheckShopIsOpen(int shopId)
    {
        return _shops[shopId];
    }
}
複製代碼

這適用於預約義的 id 列表,以及商店是開仍是關。

可是若是您在測試中使用它並傳入 1 的 id,從測試中並不能當即清楚爲何獲得 true 的響應,可能須要回來看看你的硬編碼?

那麼如何使用模擬對象來解決這個問題?(固然建議你直接使用 Moq 庫!):

var _mockShopService = new Mock<IShopService>();
_mockShopService.Setup(x => x.CheckShopIsOpen(1)).Returns(true);
複製代碼

就在測試代碼中,當使用模擬對象 ID 爲 1 的 CheckShopIsOpen 時,很是清楚,返回 true。

它也是特定於這個測試的,而且不會強迫咱們在任何地方硬編碼任何東西,或者建立具體的類。

當咱們有一個測試要求商店 id 1 爲假時..

_mockShopService.Setup(x => x.CheckShopIsOpen(1)).Returns(false);
複製代碼

so easy!

🎏 4. 什麼時候使用 Mock、Fake 和 Stub

一切並非絕對的,你懂得! 在這裏插入圖片描述

  • 當想要一個可重用的具體實現時,請使用 Fake,該實現與真實實現相似,具備跨測試的可重用性(例如內存數據庫)
  • 當想要在測試中重複使用的硬編碼響應/實現時使用存根Stub
  • 當須要對單個測試進行動態響應時使用 Mock

不過,通常測試可能並不會分的這麼嚴格,大部分狀況下,我只使用Mock!不服來戰~~~~

好了,但願您能更好地理解這些測試對象的用途,並對什麼時候使用每一個對象有更多的瞭解。

最後,祝你好運!

🎏 05. 小結

例行小結,理性看待。

👓都看到這了,還在意點個贊嗎?

👓都點讚了,還在意一個收藏嗎?

👓都收藏了,還在意一個評論嗎?

相關文章
相關標籤/搜索