测试中 Fakes、Mocks 以及 Stubs 概念明晰
自动化测试中,测试我们常会使用一些经过简化的念明,行为与表现类似于生产环境下的测试对象的复制品。引入这样的念明复制品能够降低构建测试用例的复杂度,允许我们独立而解耦地测试某个模块,测试不再担心受到系统中其他部分的念明影响;这类型对象也就是所谓的 Test Double。实际上对于 Test Double 的测试定义与阐述也是见仁见智,Gerard Meszaros 在这篇文章中就介绍了五个不同的念明 Double 类型;而人们更倾向于使用 Mock 来统一描述不同的 Test Doubles。不过对于 Test Doubles 实现的测试误解还是可能会影响到测试的设计,使测试用例变得混乱和脆弱,念明最终带来不必要的测试重构。本文则是念明从作者个人的角度描述了常见的 Test Doubles 类型及其具体的实现:Fake、Stub 与 Mock,测试并且给出了不同的企商汇念明 Double 的使用场景。

Fake
Fakes are 测试objects that have working implementations, but not same as production one. Usually they take some shortcut and have simplified version of production code.Fake 是那些包含了生产环境下具体实现的简化版本的对象。如下图所示,Fake 可以是某个 Data Access Object 或者 Repository 的基于内存的实现;该实现并不会真的去进行数据库操作,而是使用简单的 HashMap 来存放数据。这就允许了我们能够在并没有真的启动数据库或者执行耗时的外部请求的情况下进行服务的测试。
@Profile("transient") public class FakeAccountRepository implements AccountRepository { Map<User, Account> accounts = new HashMap<>(); public FakeAccountRepository() { this.accounts.put(new User("john@bmail.com"), new UserAccount()); this.accounts.put(new User("boby@bmail.com"), new AdminAccount()); } String getPasswordHash(User user) { return accounts.get(user).getPasswordHash(); } }除了应用到测试,Fake 还能够用于进行原型设计或者峰值模拟中;我们能够迅速地实现系统原型,并且基于内存存储来运行整个系统,推迟有关数据库设计所用到的一些决定。另一个常见的使用场景就是利用 Fake 来保证在测试环境下支付永远返回成功结果。
Stub
Stub is an object that holds predefined data and uses it to answer calls during tests. It is used when we cannot or don’t want to involve objects that would answer with real data or have undesirable side effects.Stub 代指那些包含了预定义好的数据并且在测试时返回给调用者的对象。Stub 常被用于我们不希望返回真实数据或者造成其他副作用的场景。WordPress模板Stub 的典型应用场景即是当某个对象需要从数据库抓取数据时,我们并不需要真实地与数据库进行交互或者像 Fake 那样从内存中抓取数据,而是直接返回预定义好的数据。

我们在编写测试用例时并没有从 Gradebook 存储中抓取数据,而是在 Stub 中直接定义好需要返回的成绩列表;我们只需要足够的数据来保证对平均值计算函数进行测试就好了。
public class GradesServiceTest { private Student student; private Gradebook gradebook; @Before public void setUp() throws Exception { gradebook = mock(Gradebook.class); student = new Student(); } @Test public void calculates_grades_average_for_student() { when(gradebook.gradesFor(student)).thenReturn(grades(8, 6, 10)); //stubbing gradebook double averageGrades = new GradesService(gradebook).averageGrades(student); assertThat(averageGrades).isEqualTo(8.0); } }Command Query Separation
仅返回部分结果而并没有真实改变系统状态的的方法被称作查询(Query)。譬如 avarangeGrades,用于返回学生成绩平均值的函数就是非常典型的例子:Double getAverageGrades(Student student);。该函数仅返回了某个值,而没有其他的任何副作用。正如我们上文中介绍的,我们可以使用 Stubs 来替换提供实际成绩值的函数,从而简化了整个测试用例的编写。不过除了 Query 之外还有另一个类别的方法,被称作 Command。即当某个函数在执行某些操作的免费源码下载时候还改变了系统状态,不过该类型函数往往没有什么返回值:void sendReminderEmail(Student student);。这种对于方法的划分方式也就是 Bertrand Meyer 在Object Oriented Software Construction 一书中介绍的 Command Query 分割法。
对于 Query 类型的方法我们会优先考虑使用 Stub 来代替方法的返回值,而对于 Command 类型的方法的测试则需要依赖于 Mock。
Mock
Mocks are objects that register calls they receive. In test assertion we can verify on Mocks that all expected actions were performed.Mocks 代指那些仅记录它们的调用信息的对象,在测试断言中我们需要验证 Mocks 被进行了符合期望的调用。当我们并不希望真的调用生产环境下的代码或者在测试中难于验证真实代码执行效果的时候,我们会用 Mock 来替代那些真实的对象。典型的例子即是对邮件发送服务的测试,我们并不希望每次进行测试的时候都发送一封邮件,毕竟我们很难去验证邮件是否真的被发出了或者被接收了。我们更多地关注于邮件服务是否按照我们的预期在合适的业务流中被调用,其概念如下图所示:

在上述代码中,我们并不想真的去关门来测试 securityOn 方法,因此我们可以设置合适的 Mock 对象:
public class SecurityCentralTest { Window windowMock = mock(Window.class); Door doorMock = mock(Door.class); @Test public void enabling_security_locks_windows_and_doors() { SecurityCentral securityCentral = new SecurityCentral(windowMock, doorMock); securityCentral.securityOn(); verify(doorMock).close(); verify(windowMock).close(); } }在 securityOn 方法执行之后,window 与 door 的 Mock 对象已经记录了所有的交互信息,这就允许我们能够去验证 Window 与 Door 是否被真实的调用。或许有人会疑问是否在真实环境下门与窗是否被真的关闭了?其实我们并不能保证,不过这也不是我们关注的点,也不是 SecurityCentral 这个类关注的目标。门与窗是否能被正常的关闭应该是由 Door 与 Window 这两个类所关注的。
【本文是专栏作者“张梓雄 ”的原创文章,如需转载请通过与作者联系】
戳这里,看该作者更多好文
相关文章
详解如何使用U盘重装戴尔系统(一步步教你重装戴尔系统,轻松搞定电脑问题)
摘要:重装操作系统是解决电脑问题的常见方法之一,而使用U盘重装戴尔系统更为方便快捷。本文将详细介绍使用U盘重装戴尔系统的步骤和注意事项,让您轻松解决电脑故障。准备工作:备份重要数...2025-11-05
使用 K8s/Istio/Cert-manager 和 Vault 保障应用的 Tls 安全
前言Vault 是安全应用维护人员最喜欢的 hashcorp 产品之一 。Vault 是存储机密、证书、管理策略、加密数据等内容的安全工具。Vault 使用受信任的身份集中密码和控制访问权限,以此减少2025-11-05
反射是大多数语言里都必不不可少的组成部分,对象可以通过反射获取他的类,类可以通过反射拿到所有方法包括私有),拿到的方法可以调用,总之通过“反射”,我们可以将Java这种静态语言附加上动态特性。什么是反2025-11-05- 复制data/hbase/runtime/namespace ├──current │├──VERSION │├──edits_0002025-11-05
苹果电脑imazing安装教程(轻松安装imazing,管理苹果设备更便捷)
摘要:imazing是一款功能强大的苹果设备管理软件,它可以让用户更便捷地管理自己的iPhone、iPad和iPodTouch等设备。本文将为大家详细介绍如何在苹果电脑上安装imazin...2025-11-05
黑客从Wintermute加密货币做市商处窃取1.62亿美元
Bleeping Computer 网站披露,数字资产交易公司 Wintermute 首席执行官 Evgeny Gaevoy 宣布 DeFi 相关业务遭到黑客攻击,损失了约 1.622 亿美元。Win2025-11-05

最新评论