单元测试

单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

单元测试的好处

  • 提升软件质量 优质的单元测试可以保障开发质量和程序的鲁棒性。越早发现的缺陷,其修复的成本越低
  • 促进代码优化 会不断去审视自己的代码,从而(潜意识)去优化自己的代码
  • 提升研发效率 表面上是占用了项目研发时间,但是在后续的联调、集成、回归测试阶段,单测覆盖率高的代码缺陷少、问题已修复,有助于提升整体的研发效率
  • 增加重构自信 代码的重构一般会涉及较为底层的改动,比如修改底层的数据结构等,上层服务经常会受到影响;在有单元测试的保障下,我们对重构出来的代码会多一份底气

单元测试基本原则

宏观上,单元测试要符合 AIR 原则

  • A: Automatic(自动化)
  • I: Independent(独立性)
  • R: Repeatable(可重复)

微观上,单元测试代码层面要符合 BCDE 原则:

  • B: Border 边界性测试,包括循环边界、特殊取值、特殊时间点、数据顺序等
  • C: Correct 正确的输入,并且得到预期的结果
  • D: Design 与设计文档相符合,来编写单元测试
  • E: Error 单元测试的目的是为了证明程序有错,而不是证明程序无错 为了发现代码中潜藏的错误,我们需要在编写测试用例时有一些强制的错误输入(如非法数据、异常流程、非业务允许输入等)来得到预期的错误结果

如何验证单元测试的有效性

  1. 是否符合基本测试方法(边界值,条件组合)
  2. 故障注入是否能被断言有效检出

应用场景

  • 开发前写单元测试,通过测试描述需求,由测试驱动开发。(如果不熟悉TDD的同学可以去google一下)
  • 在开发过程中及时得到反馈,提前发现问题
  • 应用于自动化构建或持续集成流程,对每次代码修改做回归测试。(CI/CD 质量保障)
  • 作为重构的基础,验证重构是否可靠

常见痛点

  • 测试上下文依赖外部服务(如数据库服务)
  • 测试上下文存在代码依赖(如框架等)
  • 单元测试难以维护和理解(语义不清)
  • 对于多场景不同输入输出的函数,单元测试代码量会很多

写单元测试的难易程度跟代码的质量关系最大,并且是决定性的。项目里无论用了哪个测试框架都不能解决代码本身难以测试的问题

《重构-改善既有代码的设计》《修改代码的艺术》 《敏捷软件开发:原则、模式与实践》

代码执行过程

代码执行过程可以看做一个数据分类,处理的过程.代码异常通常也都能归类到数据分类错误和数据处理错误两种错误原因上.

为实现正确的业务逻辑代码,考虑过程:

  • 实现正确的功能逻辑的正常输入
  • 特殊处理的多种边界输入
  • 潜在的非法输入

单元测试详解

单元测试用例是一个”输入数据”,”输出数据”的集合. 针对确定的数据,根据逻辑功能预先推算出正确的输出,并以执行被测代码的方式来进行验证.

在明确代码需要实现的逻辑功能基础上,设计输入数据,并推算出输入数据应该对应的输出

输入数据不仅仅是函数入参:

  • 输入参数
  • 内部需要读取的全局静态变量
  • 内部需要读取的成员变量
  • 函数调用内部子函数获取的数据
  • 函数内部调用子函数改写的数据

输出数据,主要包含数据改写和动作执行

  • 被测函数的返回值
  • 被测函数的输出参数
  • 被测函数改写的成员变量
  • 被测函数改写的成员变量
  • 被测函数进行的文件更新
  • 被测函数进行的数据更新
  • 被测函数进行的消息队列更新

设计单元测试应该首先定义操作行为,而不是根据代码实现填充行为判定.要脱离代码从行为需要编写断言

驱动代码,桩代码,Mock代码

驱动代码用来调用被测函数,桩代码和mock代码用来代替被测函数调用的真实代码.

驱动代码(Driver)

指调用被测函数的代码. 驱动通常包含 数据准备,调用及验证三个步骤

桩代码(Stub)

用来代替真实代码的临时代码

Mock代码

Mock 和 Stub区别

  1. 代码要做到功能逻辑正确,必须做到分类正确并且完备无遗漏,同时每个分类的处理逻辑必须正确;
  2. 单元测试是对软件中的最小可测试单元在与软件其他部分相隔离的情况下进行的代码级测试;
  3. 桩代码起到了隔离和补齐的作用,使被测代码能够独立编译、链接,并运行。

单元测试复杂性

单元测试复杂性主要集中在 输入参数,预期输出,依赖代码不可用三方面

输入参数

这里的输入参数不是狭义的参数,而是影响程序最终执行结果的各种依赖

  • 被测试函数的输入参数
  • 被测试函数内部需要读取的全局静态变量
  • 被测试函数内部需要读取的类成员变量
  • 函数内部调用子函数获得的数据
  • 函数内部调用子函数改写的数据

预期输出

通常来讲,“预期输出”应该包括被测函数执行完成后所改写的所有数据,主要包括:被测函数的返回 值,被测函数的输出参数,被测函数所改写的成员变量和全局变量,被测函数中进行的文件更新、数据库更新、消息队列更新等

关联依赖代码不可用

文章链接 https://fangzongzhou.github.io/2020/12/14/计算机/软件测试/单元测试/单元测试/