《有效的单元测试》一3.3 使用测试替身的指南

简介:

本节书摘来自华章出版社《有效的单元测试》一书中的第3章,第3.3节,作者 (芬)Lasse Koskela,更多章节内容可以访问云栖社区“华章计算机”公众号查看

3.3 使用测试替身的指南

测试替身是程序员的工具,就像木匠的锤子和钉子。存在敲钉子的适当方式,当然也有不恰当的方式——最好是能把它们识别出来。
先从我认为最重要的指南开始吧,当你求助于测试替身时要时刻牢记它——从你的工具箱中选择合适的工具。

3.3.1 为测试挑选合适的替身

有许多测试替身可供选择,它们看起来各有千秋。采用它们的最佳条件是什么?到底应该选择哪个?
这里并没有太多的硬性规定,但一般来说你应该因地制宜地混合使用。我是说,某些情况下你只想要“一个返回5的对象”,而其他情况下你特别想知道某个方法被调用过。有时在一个测试中对两者都感兴趣,于是你将Stub、Fake和Mock一同使用。
前面已经说过,并没有清晰的原则来决定采用哪种方式以得到最可读的测试。但我还是忍不住对如何选择这个问题阐述一些逻辑和启发:
如果你关心某些交互,即两个对象之间的方法调用,你可能会需要一个模拟对象Mock。
如果你决定使用Mock,但测试代码最终看起来不像你想的那样漂亮,那就看看一个手工的简单测试间谍Spy能否满足需要。
如果你只关心协作对象向被测对象输送的响应,用桩Stub就可以。
如果你想运行一个复杂场景,其中它所依赖的服务或组件无法供测试使用,而你对所有交互打桩的快速尝试却戛然而止,或产出了难以维护的糟糕的测试代码,那就考虑实现一个伪造对象Fake吧。
如果上述都不能满足你手上的特殊情况,那就抛硬币吧——正面代表Mock,反面代表Stub,如果硬币直立,我允许你找一个Fake帮你干活。
如果觉得那个列表太难记,别怕。《JUnit Recipes》(Manning,2004)的作者J.?B. Rainsberger有一个简单的记忆规则,用于选择正确的测试替身类型:Stub管查询,Mock管操作。现在我们顺利地得到启发,知道什么时候该用哪种测试替身了,接下来看看如何使用它们。

3.3.2 准备、执行、断言

关于编码约定(convention),我要说几句。问题是各种标准太多了。幸运的是,当你构造单元测试时,存在一个大多数程序员都认为合理的、相当确定的实践。它叫做准备-执行-断言(Arrange-Act-Assert),这种组织测试的方式基本上是这样的,先准备用于测试的对象,然后触发执行,最后对输出进行断言。
代码清单3.8复制了代码清单3.7的测试,我们看它如何符合这种约定来组织测试方法。
image
image

注意我在三段代码之间增加空白的方式。这用来强调三段代码的不同角色。
测试的前五行是准备所要用到的协作对象。虽然其中我们只涉及Internet接口的一个Mock,但是在测试开头设置多个协作者的情况也很常见。然后是被测对象Translator——对它的实例化也是准备工作的一部分。
下一段代码中,我们调用translation(被测的翻译功能),最后,不论预期输出是直接输出还是造成的副作用,我们都对它进行断言。
给定-当-那么(Given,When,Then)
行为驱动开发运动所推广的词汇和结构与“准备-执行-断言”很像:给定(某个上下文),当(发生某些事情),那么(期望某些结果)。这个想法以更加直观的语言来指定预期行为,尽管“准备-执行-断言”更好记,但“给定-当-那么”更流畅,使人们更加自然地思考行为(而不是实现细节)。
这种结构相当普遍,它有助于使测试保持专注。如果感觉三部分中某一部分很“大”,那就是一个信号,表明测试可能试图做太多事情,需要更加专注。既然说到这话题,咱们就简单讨论一下测试应该专注什么。

3.3.3 检查行为,而非实现

人都会犯错误。模拟对象库新手常犯的一个错误是过度细致地对Mock设置期望。我指的是在测试中,对测试可能涉及的每个对象都做Mock,每个对象间的方法调用都严格指定。
是的,某种意义上,测试给予我们确定性,只要有任何变更它就会中断并报警。而这也是问题所在——即使是最小的变更,哪怕它与测试所要验证的不相关,也会中断测试。好比在一片口香糖上密密麻麻地敲了许多钉子,使之动弹不得。
这种测试的基本问题是缺乏专注。一个测试应当只测试一件事情,并好好地测试,清晰地沟通自己的意图。看着被测对象,你要问自己到底什么是想要验证的预期行为?至于实现细节,倒是并不需要钉在我们的测试中。
预期行为应该配置在Mock对象的期望中。应该寻求通过Stub或非严格Mock来提供实现细节,它们不介意交互从未发生或者发生多次。
检查行为,而非实现。当你掏出喜爱的模拟对象库时,你应该牢牢记住这一点。说到这里……

3.3.4 挑选你的工具

说到模拟对象库,Java程序员真是占了大便宜——有太多可以选择的。像我之前提到的,你几乎可以用任何先进的库来做同样的事情,但是它们在API方面还是有些细微的区别以及一些独特的功能,从而在满足某些特定方向和需求时能够一锤定音。
或许其中最独特的功能就是Mockito的打桩与验证分离。这得细说,接下来看个例子,我用Mockito重写了之前采用JMock的测试:
image

Mockito的API比JMock更简洁。除此之外,看起来差不多,是不是?是的,只是这个用Mockito写的测试仅仅对方法get()打桩——即使交互从未发生,它也会成功通过。如果我们真的希望验证Translator使用Internet的行为,我们就得增加一个对Mockito API的调用来进行检查:
image

或者,如果感觉不必那么精确:
image

模拟对象库API通常是个人喜好问题。但是Mockito在测试风格上有一个明显的优势,那就是主要依赖于打桩——在你的特定上下文中这可能是优势,也可能不是。测试代码每天都保持可读、简洁、可维护,这才是关键。这值得停下来权衡一下,明智地选择工具。
咱们再次借用J.?B.的话来明确JMock与Mockito在方式和适用条件方面的区别:
当我想要拯救遗留代码时,我选择Mockito。当我想要设计新功能时,我选择JMock。
JMock与Mockito不同的前提假设,使得两者擅长不同的任务。默认情况下,JMock认为测试替身(Mock)期望着客户不会在任何时候调用任何方法。如果你想放宽这个假设,你就得增加一个stub。另一方面,Mockito认为测试替身(也叫Mock)允许客户在任何时候调用任何方法。如果你想加强这个假设,那么你就得验证某个方法的调用。这就是区别所在。
不论你决定选择哪个库,我们的第三个即最后一个测试替身指南全都适用。

3.3.5 注入依赖

为了能够使用测试替身,你需要一种替换真实事物的方法。当涉及依赖时——为了测试目的而替换协作对象——我们的指南建议不要在同一个地方同时实例化和使用它们。在实践中,这意味着将这些对象另存为私有成员,或借助工厂方法来获取它们。
一旦你隔离开依赖,你就需要访问它。你可以用可见性修饰符来破坏封装——将私有(private)内容变成公开(public)或者包级私有(package private)——或使用反射API来将测试替身分配给私有字段。那种方式很快就会变得丑陋。更好的选择是采用依赖注入,从外部将依赖传递给对象,通常使用构造函数注入,正如在Translator例子中那样。
我们对于测试替身说得够多了,我渴望进行第二部分了,接下来对本章学到的东西做一个回顾吧。

相关文章
|
测试技术 开发者 UED
探索软件测试的深度:从单元测试到自动化测试
【10月更文挑战第30天】在软件开发的世界中,测试是确保产品质量和用户满意度的关键步骤。本文将深入探讨软件测试的不同层次,从基本的单元测试到复杂的自动化测试,揭示它们如何共同构建一个坚实的质量保证体系。我们将通过实际代码示例,展示如何在开发过程中实施有效的测试策略,以确保软件的稳定性和可靠性。无论你是新手还是经验丰富的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。
|
IDE 测试技术 持续交付
Python自动化测试与单元测试框架:提升代码质量与效率
【9月更文挑战第3天】随着软件行业的迅速发展,代码质量和开发效率变得至关重要。本文探讨了Python在自动化及单元测试中的应用,介绍了Selenium、Appium、pytest等自动化测试框架,以及Python标准库中的unittest单元测试框架。通过详细阐述各框架的特点与使用方法,本文旨在帮助开发者掌握编写高效测试用例的技巧,提升代码质量与开发效率。同时,文章还提出了制定测试计划、持续集成与测试等实践建议,助力项目成功。
280 5
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
792 0
|
测试技术 Java Spring
Spring 框架中的测试之道:揭秘单元测试与集成测试的双重保障,你的应用真的安全了吗?
【8月更文挑战第31天】本文以问答形式深入探讨了Spring框架中的测试策略,包括单元测试与集成测试的有效编写方法,及其对提升代码质量和可靠性的重要性。通过具体示例,展示了如何使用`@MockBean`、`@SpringBootTest`等注解来进行服务和控制器的测试,同时介绍了Spring Boot提供的测试工具,如`@DataJpaTest`,以简化数据库测试流程。合理运用这些测试策略和工具,将助力开发者构建更为稳健的软件系统。
212 0
|
测试技术 Java
全面保障Struts 2应用质量:掌握单元测试与集成测试的关键策略
【8月更文挑战第31天】Struts 2 的测试策略结合了单元测试与集成测试。单元测试聚焦于单个组件(如 Action 类)的功能验证,常用 Mockito 模拟依赖项;集成测试则关注组件间的交互,利用 Cactus 等框架确保框架拦截器和 Action 映射等按预期工作。通过确保高测试覆盖率并定期更新测试用例,可以提升应用的整体稳定性和质量。
202 0
|
测试技术 数据库
探索JSF单元测试秘籍!如何让您的应用更稳固、更高效?揭秘成功背后的测试之道!
【8月更文挑战第31天】在 JavaServer Faces(JSF)应用开发中,确保代码质量和可维护性至关重要。本文详细介绍了如何通过单元测试实现这一目标。首先,阐述了单元测试的重要性及其对应用稳定性的影响;其次,提出了提高 JSF 应用可测试性的设计建议,如避免直接访问外部资源和使用依赖注入;最后,通过一个具体的 `UserBean` 示例,展示了如何利用 JUnit 和 Mockito 框架编写有效的单元测试。通过这些方法,不仅能够确保代码质量,还能提高开发效率和降低维护成本。
159 0
|
Java 测试技术 API
SpringBoot单元测试快速写法问题之复杂的业务逻辑设计有效的单元测试如何解决
SpringBoot单元测试快速写法问题之复杂的业务逻辑设计有效的单元测试如何解决
|
Java 测试技术 API
SpringBoot单元测试快速写法问题之计算测试用例的分支覆盖率如何解决
SpringBoot单元测试快速写法问题之计算测试用例的分支覆盖率如何解决
|
11月前
|
数据可视化 前端开发 测试技术
接口测试新选择:Postman替代方案全解析
在软件开发中,接口测试工具至关重要。Postman长期占据主导地位,但随着国产工具的崛起,越来越多开发者转向更适合中国市场的替代方案——Apifox。它不仅支持中英文切换、完全免费不限人数,还具备强大的可视化操作、自动生成文档和API调试功能,极大简化了开发流程。
|
6月前
|
Java 测试技术 容器
Jmeter工具使用:HTTP接口性能测试实战
希望这篇文章能够帮助你初步理解如何使用JMeter进行HTTP接口性能测试,有兴趣的话,你可以研究更多关于JMeter的内容。记住,只有理解并掌握了这些工具,你才能充分利用它们发挥其应有的价值。+
1087 23

热门文章

最新文章