這是我參與8月更文挑戰的第6天,活動詳情查看:8月更文挑戰web
- 📢歡迎點贊 :👍 收藏 ⭐留言 📝 若有錯誤敬請指正,賜人玫瑰,手留餘香!
- 📢本文做者:由webmote 原創,首發於 【CSDN】
- 📢做者格言: 生活在於折騰,當你不折騰生活時,生活就開始折騰你,讓咱們一塊兒加油!💪💪💪
單元測試中有幾個神祕的概念,它們就是Mock,模擬對象;Stub,存根;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
這是一個「真正的」實現,實際上就像一個存儲庫同樣,只是在幕後沒有實際的數據庫。
存根是一種返回硬編碼響應
的實現.
存根沒有任何「智能」。沒有將對象上的調用捆綁在一塊兒,而是每一個方法只返回一個預約義的固定響應。
讓咱們看看如何爲上述建立存根:
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;
}
複製代碼
沒有是否應該對用戶進行身份驗證的智能算法,沒有其餘值,只是一個直接的「老是返回真」的方法。
固定,就是存根擅長的地方。
模擬是一個預設的對象,能夠將動態響應/行爲定義爲測試的一部分,並預先定義好。
它們不須要去特別實現或實例化,而且(一般)不須要在測試之間共享行爲。
咱們將在哪裏使用 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!
一切並非絕對的,你懂得!
不過,通常測試可能並不會分的這麼嚴格,大部分狀況下,我只使用Mock!不服來戰~~~~
好了,但願您能更好地理解這些測試對象的用途,並對什麼時候使用每一個對象有更多的瞭解。
最後,祝你好運!
例行小結,理性看待。
👓都看到這了,還在意點個贊嗎?
👓都點讚了,還在意一個收藏嗎?
👓都收藏了,還在意一個評論嗎?