单元测试有助于避免尴尬、耗时的错误,将测试作为安全网只是一部分,更大部分是将测试表达为代码的思考过程。

  接下来的内容提炼自《单元测试的艺术(第2版)》和《有效的单元测试》两本书。

一、质疑和回答

       在组内推广时,进度并不理想,遇到的阻碍大致可归纳为以下这几种情况:

  • 首先就是团队成员会质疑单元测试的价值,需要给出证明单元测试确实有效可行的方法和证据。
  • 其次是团队成员缺乏主动测试的意识,目前有大量的测试代码,不知道从哪里开始测试,并且花费额外的精力来维护单元测试的代码。
  • 还有就是编写单元测试大概会花费日常开发的 10% 以上的时间,而项目时间总是比较紧,无法留出充裕的测试时间。

       针对第一个问题,可用一个实验回答。让两个在技术和经验上近似的团队,分别负责两个规模近似的项目,其中一个进行单元测试,另一个不进行单元测试的时间差。

       下表是两个团队的进度和输出度量:

 
阶段
不进行单元测试的团队
进行单元测试的团队
实施 7天 14天
集成 7天 2天
测试和修复缺陷

测试:3天

修复:3天

测试:3天

修复:2天

测试:1天

总计:12天

测试:3天

修复:1天

测试:1天

修复:1天

测试:1天

总计:7天

整体交付时间 26天 23天
客户发现的缺陷数 71 11

       针对第二个问题,可引用一份研究报告,20世纪70和90年代进行的研究表明:通常,20%的代码包含了90%的缺陷。如果能找到这20%的代码,那么就能大大提升测试效率。

       但困难的就是如何找到包含最多问题的代码。其实任何团队都能告诉你哪个组件问题最多,那你就可以从这个组件开始测试。

       针对第三个问题,目前的办法是多写多测,让单元测试成为开发的一部分,不要苛求测试覆盖率,先就测试影响业务流程的核心代码。

二、测试替身

       测试替身的作用是隔离被测代码,加速执行测试,使执行变得确定,模拟特殊情况,暴露隐藏信息。

       其中隔离被测代码,使测试有针对性和容易理解,而利用测试替身实现的隔离,还有个副作用,那就是测试替身的速度要比本尊快很多。

       测试替身的类型:

  • Stub(测试桩):一个对象的所有方法只有一行,且各自返回一个适当的默认值。使用场景:只关心协作对象输送的响应。
  • Fake(伪造对象):可以返回硬编码值,而每个测试可能需要有差异地实例化来返回不同值,模拟不同场景。使用场景:所依赖的服务或组件无法供测试使用,打桩产生了难以维护的糟糕代码。
  • Spy(测试间谍):用于记录过去发生的情况,这样测试在事后就知道所发生的一切。使用场景:将其作为参数传递被测函数中。
  • Mock(模拟对象):是特殊的Spy,在一个特定情景下可配置行为的对象。使用场景:关心某些交互,即两个对象之间的方法调用。

三、设计指南

  • 避免测试中包含逻辑,不应该有switch、if-else等判断语句,for、while等循环语句。以免测试难以阅读和理解,难以复现,难以命名。
  • 只测试一个关注点,一个工作单元只有一个最终结果,例如一个返回值、系统状态的一个改变或对第三方对象的一个调用。
  • 专注检查行为而非实现,避免过度指定,只需检查最终行为的正确性即可,既不要使用多个模拟对象,也不要对一个被测对象的纯内部状态进行断言。
  • 避免复杂的私有方法,不要直接测试 private 方法。
  • 避免在构造函数中包含需要测试的代码逻辑。
  • 避免单例,单例模式会妨碍创建不同的变体。
  • 使用 new 时要当心,实例化的对象,应该仅限于不会替换为测试替身的对象。

四、测试坏味道

1)可读性

       程序员用测试的方式来表达和验证代码的假设和预期行为。

       阅读测试代码之后,就该理解代码应当做什么。程序员运行那些测试时,就该了解代码实际上在做什么。

  1. 问题:基本断言缺乏意义,因为断言的基本原理和意图隐藏在看上去无意义的单词和数字背后,造成难以理解。
  2. 改进:去掉魔法数字,改用断言方法,使用编程语言内置的 API 语法。
  1. 问题:过度断言很脆弱,并且掩盖了整体广度和深度之下的意图。
  2. 改进:识别无关细节并移除。
  1. 问题:人格分裂是指一个测试检查了多件事。
  2. 改进:去掉重复,将粗粒度的场景分离。
  1. 问题:过分保护是指在测试开头增加守卫语句和空值检查保护自己。
  2. 改进:去除冗余断言,检查要使真正的断言通过所需的中间条件。

2)可维护性

       代码从不慢慢退化,而是直接奔溃。

       测试也是如此,同样脆弱,程序员编写自动化的单元测试来尽可能地管理这种脆弱性。

       大家都知道维护噩梦是什么,你绝对不希望你的测试代码沦落其中。

  1. 问题:重复最明显的是某一个数字或字符串在代码中反复出现。
  2. 改进:将可变数据提炼到局部变量中。
  1. 问题:残缺的文件路径会使代码无法转移,只能在某个人的计算机中。
  2. 改进:避免绝对路径,选择相对路径,用流来替换文件。
  1. 问题:像素完美出现的场景包括期望和实际产生的图像完美匹配。
  2. 改进:用适当的抽象层次来表达测试,将背后的细节隐藏到自定义断言中,进行模糊匹配。

3)可信度

       软件开发其实就是在修改、演进和维护代码,如果不能信任测试,那么在即使看似最无辜的改动之后,仍然不能确信代码是否能够工作。

       接下来会围绕测试不可靠的问题来检阅测试坏味道。

  1. 问题:永不失败的测试不具有价值,给你虚假的安全感。
  2. 改进:养成运行测试的习惯,例如临时修改被测代码来故意触发一次失败。
  1. 问题:轻率承诺是指测试实际上没有测试任何东西,或名不符实。
  2. 改进:确保断言了一些事情,确切找出要检查的行为,也更容易命名。
  1. 问题:降低期望就是降低了确定性与精确性的标准。
  2. 改进:提高门槛,使测试的期望更具体。
  1. 问题:有条件的测试是在一个测试方法内隐藏了秘密条件,使测试逻辑名不符实。
  2. 改进:确保测试在每个条件分支时都有机会失败。

 

 posted on 2021-10-29 09:53  咖啡机(K.F.J)  阅读(423)  评论(0编辑  收藏  举报