从零开始搞基建(4)——单元测试

简介:   单元测试有助于避免尴尬、耗时的错误,将测试作为安全网只是一部分,更大部分是将测试表达为代码的思考过程。  接下来的内容提炼自《单元测试的艺术(第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 语法。
  3. 问题:过度断言很脆弱,并且掩盖了整体广度和深度之下的意图。
  4. 改进:识别无关细节并移除。
  5. 问题:人格分裂是指一个测试检查了多件事。
  6. 改进:去掉重复,将粗粒度的场景分离。
  7. 问题:过分保护是指在测试开头增加守卫语句和空值检查保护自己。
  8. 改进:去除冗余断言,检查要使真正的断言通过所需的中间条件。

2)可维护性

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

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

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

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

3)可信度

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

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

  1. 问题:永不失败的测试不具有价值,给你虚假的安全感。
  2. 改进:养成运行测试的习惯,例如临时修改被测代码来故意触发一次失败。
  3. 问题:轻率承诺是指测试实际上没有测试任何东西,或名不符实。
  4. 改进:确保断言了一些事情,确切找出要检查的行为,也更容易命名。
  5. 问题:降低期望就是降低了确定性与精确性的标准。
  6. 改进:提高门槛,使测试的期望更具体。
  7. 问题:有条件的测试是在一个测试方法内隐藏了秘密条件,使测试逻辑名不符实。
  8. 改进:确保测试在每个条件分支时都有机会失败。
相关文章
|
Java 测试技术 Python
关于如何写自动化测试框架,看完本章你会有收获
关于如何写自动化测试框架,看完本章你会有收获
117 0
|
运维 小程序 数据可视化
不用写代码也能开发,产品经理是怎么做到的?
不用写代码也能开发,产品经理是怎么做到的?
|
设计模式 程序员 开发者
程序员在开发中必经之路:重构代码
众所周知,程序员在开发过程中接手前人代码,或者接手公司外购项目的代码等情况的时候,都有想要重构代码的冲动,与其这样说,不如说程序员只要是接手不是自己亲自写的代码都想重构!俗话说得好:一百个程序员脑中有一百个编程思维,不同程序员就算是开发相同功能的程序,一定会有不同的实现方式,而且代码格式和实现方式也肯定是不一样的,这样就给程序的代码重构留下了伏笔。
164 1
|
JavaScript 前端开发 数据库
从零开始搞基建(5)——代码质量
从零开始搞基建(5)——代码质量
|
敏捷开发 Devops 测试技术
深聊测开领域之:一文搞懂什么是敏捷测试,如何做敏捷测试,建议先收藏再学习。
深聊测开领域之:一文搞懂什么是敏捷测试,如何做敏捷测试,建议先收藏再学习。
817 0
深聊测开领域之:一文搞懂什么是敏捷测试,如何做敏捷测试,建议先收藏再学习。
|
前端开发 jenkins 测试技术
自动化测试技术笔记(一):前期调研怎么做
虽然说自动化测试比较偏技术工作,但在开展前,明确你的工作目标和KPI也是不可忽视的一点。并不是说技术优秀就可以拿到好的绩效,企业生存第一法则是先活下来做产出,再考虑锦上添花和技术优化的事。
|
自然语言处理 Java 测试技术
告别祈祷式编程|单元测试在项目里的正确落地姿势
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Jav...
178 1
|
JSON Java 测试技术
谈一谈单元测试
写在前面对于我们开发人员来说,单元测试一定不会陌生,但在各种原因下会被忽视,尤其是在我接触到的项目中,提测阶段发现各种各样的问题,我觉得有必要聊一下单元测试。对于单元测试到底有没有存在的必要,这里不是我想要说的重点。有兴趣的可以去了解一下:【单元测试和TDD】【单元测试到底是什么】为了写而写的单元测试没什么价值,但一个好的单元测试带来的收益是非常客观的。问题是怎么去写好单元测试?怎么去驱动写好单元
668 0
谈一谈单元测试
|
Java 测试技术 程序员
单元测试经典三问:是什么,为什么,怎么做?
编写合格的单元测试可以说是 Java 程序员的基本功。 很多公司对但单测覆盖率都会有要求,通常要求在 60% 到 90% 不等。 但是很多同学对单元测试或多或少有一些抵触,对如何写出“标准”的单元测试代码存在疑问。 有些同学编写单元测试,纯粹是应付工作,完全起不到单测应该起到的作用。 本文解答单元测试的三个基本问题,即单元测试是什么,为什么编写单元测试,怎么编写单元测试?
844 0
单元测试经典三问:是什么,为什么,怎么做?
|
存储 移动开发 前端开发
从零开始搞基建(1)——前端代码规范
对所有引用都使用 const,不要使用 var。原因:这样做可以确保你无法重新分配引用,以避免出现错误和难以理解的代码。 如果引用是可变动的,使用 let 代替 var。原因:let 是块级作用域的,而不像 var 属于函数级作用域。 坚持使用全等 === 摒弃相等 ==,原因:相等会进行隐式的类型转换。 使用浏览器全局变量时加上 window 前缀,document 和 navigator 除外。

热门文章

最新文章