这是我参与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!不服来战~~~~
好了,但愿您能更好地理解这些测试对象的用途,并对什么时候使用每一个对象有更多的了解。
最后,祝你好运!
例行小结,理性看待。
👓都看到这了,还在意点个赞吗?
👓都点赞了,还在意一个收藏吗?
👓都收藏了,还在意一个评论吗?