美文网首页
单元测试笔记

单元测试笔记

作者: VET周培公 | 来源:发表于2021-03-24 08:13 被阅读0次

优秀的代码是由优秀的工程师写的,与语言无关。
任何制度、任何工具,都不能取代优秀工程师的地位。
优秀代码的特征之一,就是有一键自动运行的、逻辑分支覆盖完整的、实证科学证伪的单元测试用例库。

单元测试的作用

测试金字塔从下到上,分别是单元测试(Unit Test)、集成测试(Integration Test)和UI测试(UI Test)。越往底层,测试的效率越高,测试质量保障程度越高,测试的成本越低。

  • 验证实现之前,先验证设计。针对实际使用塑造设计,从调用者的角度来审视代码的设计和行为。
  • 在开发的过程中注重代码的设计,比如拆分模块,抽象组件等等,才能保证代码的单元化和可测性。
  • 作为经济合理的回归测试手段,好的单元测试可以保证重构后代码的质量,尤其是减少影响相关系统稳定性的API兼容问题,以适应迭代开发的需要。
  • 单元测试用例一次编写多次运行,是手工到自动的进化,为提高效率打下基础。

单元测试并不是万能的

  • 单元测试只是自动化测试一个基础的、关键的节点,只是测试对应代码的功能,不包含复杂的业务逻辑,并不能代替集成测试和UI人工测试。
  • 盲目地追求测试覆盖率,忽视测试代码的质量,各种无效单元测试不但没有收益,反而带来了沉重的维护负担。
  • 有效单元测试在质量保证和系统设计两方面,边际收益是递减的。100%测试覆盖率不是目标,重点在确保写出有意义的测试。

优秀的方案都是上下文相关的,开发实践中没有绝对的真理。

找到优秀方案的最好方式就是尝试一个看似可行的方法,识别该方法的问题,然后改变该方法从而消除令人讨厌的部分。通过重复这个过程,不断的评估和进化,最终找到一个可行的方案,闻起来没有那么臭的方案。

编写测试代码

测试驱动开发(TDD)的编码流程

  1. 分析产品需求的PRD文档,设计出技术方案(评审)。
  2. 按评审过的技术方案,定义Interface,Model,Class,生成契约。
  3. 按照期望的输入和输出,为对外提供的每个功能点写测试方法,为每个测试方法写断言。
  4. 生成或编写空壳的MOCK代码,执行单元测试,确认逻辑是否符合预期。
  5. 部署测试环境,提供调用方可用的对外MOCK接口。
  6. 编写具体业务逻辑代码。
  7. 执行单元测试,确认业务逻辑是否符合预期。
  8. 迭代补充单元测试的测试方法和断言。

测试代码迭代补充的时机

  1. 第一次出错时,出错点补充进单元测试用例。
  2. 单元测试用例覆盖容易出错的地方。

优秀的单元测试

FCC属性

  • 快速(Fast):运行速度快。
  • 隔离(Isolated):相互隔离,即每个测试可以独立运行,或者作为一组测试的一部分运行,可以按任何顺序运行。
  • 无需配置(Configuration-free):不需要进行外部配置。
  • 稳定(andConsistent):产生稳定的通过或失败结果。

AIR原则

单元测试在线上运行时感觉像空气(AIR)一样不存在,在测试质量的保障上却非常关键。

好的单元测试具有自动化、独立性、可重复执行的特点:

  • A:Automatic(自动化):单元测试应该是全自动执行的,并且非交互式的。
  • I:Independent(独立性):单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
  • R:Repeatable(可重复):单元测试是可以重复执行的,不能受到外界环境的影响。

F.I.R.S.T五原则

  • Fast:测试用例需要频繁运行,因此要能快速运行,不能依赖有延迟的方法和资源。
  • Independent:测试用例之间,应该相互独立,一次只测试一个逻辑分支。
  • Repeatable:测试用例本身不应包含外部依赖逻辑,需要能在任何环境中重复运行。
  • Self-validating:只关注输入输出的证伪验证,不关注内部实现,不证实内部实现逻辑。
  • Timely:测试用例应该及时编写,用例代码要自我表达,容易阅读。

检讨测试代码

如果以下任何一个问题的回答是否定的,那么就需要检讨单元测试的编写工作:

  • 是不是可以一键自动化运行、并且可以重复运行?
  • 几个月后,是不是仍可以运行,并且可以得到期望的结果?
  • 是否可以在短时间(几分钟内)运行结束?
  • 在运行之前,是否不需要进行一系列的配置?
  • 每次运行,是否能够得到相同的结果?
  • 外部的系统因素是否不会影响你的测试结果?
  • 测试代码是否可以很简单的编写完成?

单元测试的内容

编写单元测试代码遵守BCDE原则:

  • B: Border,边界值测试,包括循环、 特殊取,边界值测试包括循环、 特殊取特殊时间点、数据顺序等。
  • C: Correct,正确的输入,并得到预期结果。 ,正确的输入并得到预期结果。
  • D: Design,与设计文档相结合,来编写单元测试。 ,与设计文档相结合来编写单元测试。
  • E: Error,强制错误信息输入(如:非法数据、异常流程业务允许等),并得 ,强制错误信息输入(如:非法数据、异常流程业务允许等),并得到预期结果。

一般来说,有六个值得测试的具体方面,可以统称为Right-BICEP:

  • Right-结果:对于单元测试测试而言,首要的也是最明显的任务就是,查看正常输入所期望的结果是否正确。
  • B-边界条件:找边界条件是做单元测试中最有价值的工作之一,因为BUG一般就出现在边界上。
  • I-检查反向关联:对于一些方法,可以使用反向的逻辑关系来验证它们。
  • C-交叉检查:有些时候,实现一个问题会有不同的算法。在生产系统中使用一种算法,而在测试中可以使用另一种算法来验证其结果是否一致。
  • E-强制产生错误条件:在实际运行过程中,有时候会发生一些意外的难以避免的错误。在测试中,应该强制引发错误,来测试代码是否能够按照预期处理这些异常。
  • P-是否满足性能条件:性能同样是我们测试过程中需要验证的指标。

代码中的BUG,经常出现在边界条件附近。对于边界条件的测试,可以从CORRECT七个方面进行考虑:

  • 一致性:值是否满足预期的格式
  • 有序性:一组值是否满足预期的排序要求
  • 区间性:值是否在一个合理的最大值最小值范围内
  • 引用、耦合性:代码是否引用了一些不受代码本身直接控制的外部因素
  • 存在性:值是否存在(例如:非Null,非零,存在于某个集合中)
  • 基数性:是否恰好具有足够的值
  • 时间性:所有事情是否都按照顺序发生的?是否在正确的时间、是否及时

什么不是单元测试?

  • 跨边界的测试
  • 违反了单一职责原则,不具有针对性的测试
  • 不可预测的测试。针对一组给定的输入参数调用一个类的方法时,单元测试的结果应当总是一致的。将不可预测的数据的功能抽象到一个可以在单元测试中模拟(Mock)的类或方法中,是好的设计原则。
  • 集成测试。在实践中,把集成测试和单元测试分离开,是很重要的。

优秀的测试代码

优秀的单元测试代码,应同时具备可靠性、可维护性和可读性。

可靠性

  • 写好的测试代码,通常是不应该修改或删除的。但是一样需要不断进行维护,修改和删除必要的测试代码。
  • 避免测试代码中的逻辑。
  • 一次只测试一个关注点。
  • 建一个绿色安全区把集成测试和单元测试分开。绿色安全区里只包含单元测试。
  • 人们通常会选择做最少的事情达到某个指定的目标,覆盖率需要与代码审查相结合。

可维护性

  • DRY原则(Don't Repeat Yourself),清理重复代码。
  • 测试代码之间需要隔离,确保每个测试都可以独立运行。
  • 使用参数化测试,避免对不同的关注点多次断言。
  • 只检查被测试单元最终行为的正确性是,而不对其内部实现进行假设。

可读性

  • 单元测试命名标准非常重要,提供合理的规则和模板,列出应该包括的测试信息。
  • 测试中的变量命名,要确保阅读测试的人可以尽快的理解你要验证什么。
  • 断言和操作分离。

SOLID原则

  • Single Responsibility Principle:单一职责原则,一个类应该只有一个发生变化的原因。
  • Open Closed Principle:开闭原则,一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。
  • Liskov Substitution Principle:里氏替换原则,所有引用基类的地方必须能透明地使用其子类的对象。
  • Law of Demeter:迪米特法则,只与你的直接朋友交谈,不跟“陌生人”说话。如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
  • Interface Segregation Principle:接口隔离原则,客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上。
  • Dependence Inversion Principle:依赖倒置原则。上层模块不应该依赖底层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

测试替身(Test Double)

在单元测试中,经常会依赖无法控制的外部对象。我们并不需要关心外部依赖项的内部实现逻辑,只需要关心是否按照接口或契约约定的入参返回了约定的出参。测试替身(Test Double)在单元测试中就可以替代真实的外部依赖项。依据国民待遇原则,我们要求外部依赖项按接口和契约提供实现的同时,我们对外提供的代码单元也应该同样的遵循面向接口契约的规则,以此类推单元测试的各代码单元之间也应该遵循同样的规则。

测试替身(Test Double)包括桩(stub)、Mock(模拟对象)、spy(间谍)、伪对象(fake)、虚拟对象(dummy)。

代码的可测性

单元测试需要保持简单。

如果发现单元测试很困难,说明代码不具备可测性,或可测性不好,需要重构代码满足可测性的要求。

设计模式的23种武器,都为重构而生。

可测试性设计要成为一种思维方式。

可测试性设计技巧

  • 在设计之初,尽量设计可重写的方法。
  • 面向接口设计,使依赖容易被测试替身替换。
  • 尽量避免测试中不能替换和重写的使用密封类。
  • 对于外部依赖的对象可以使用依赖注入的方式,避免在方法内初始化对象。
  • 避免直接调用静态方法、使用单例,因为缺少可替换性。
  • 避免在构造函数和静态构造函数中包含逻辑代码。
  • SOLID原则应用于编码中,绝大多数情况下都具有良好的可测试性。

可测试性设计的缺点

  • 以可测试性为目标会增加工作量。
  • 容易把简单的事情搞的过于复杂。
  • 为了让代码更具有可测试性,会破坏一些原有的设计原则。

UI的单元测试类型

  • 创建测试快照
  • 普通事件测试
  • 异步事件测试(如 setTimeOut)
  • dom 测试
  • redux 测试
  • context 测试
  • http 请求测试(如axios、fecth)
  • router 测试

相关文章

  • 【单元测试】

    Android学习笔记:对Android应用进行单元测试关于Android单元测试,你需要知道的一切Android...

  • VUE常用笔记

    VUE笔记 开启项目 运行 编译 单元测试 Lints and fixes files 页面常用

  • 2020-05-27 做十件事,不如做精一件事

    【目标回顾】: ? 练习册讲解&批改 完成 ? 四单元测试 完成 ? 笔记 完成 ?...

  • 简洁代码--单元测试

    代码整洁之道笔记 [TOC] 单元测试 测试驱动开发 TDD三定律 在编写不能通过的单元测试前,不能编写生产代码。...

  • C++代码整洁之道1:单元测试的重要性

    以下为《C++代码整洁之道》阅读笔记 注重单元测试 重要性就不多说了,防患于未然,构建大型系统尤其需要进行单元测试...

  • 单元测试&依赖注入

    本文仅为学习笔记;不是原创文章; 参考资料1参考资料2 一:单元测试基本概念 概念:单元测试,是为了测试某一个类的...

  • Node.js笔记六:单元测试

    Node.js笔记六:单元测试 源码github地址在此,记得点星:https://github.com/bran...

  • 单元测试基本配置

    兼容es6的mocha单元测试项目配置 笔记 test/xx.js中,describe, it中function使...

  • 单元测试

    内容 单元测试 参考文章: [iOS单元测试系列]单元测试框架选型 iOS单元测试:Specta + Expect...

  • 3/26day19_JUnit单元测试_NIO

    day19_JUnit单元测试_NIO 复习 今日内容 Junit单元测试[重点] 单元测试概念 什么叫单元测试单...

网友评论

      本文标题:单元测试笔记

      本文链接:https://www.haomeiwen.com/subject/wrnfhltx.html