单元测试

144 阅读7分钟

什么是单元测试

单元测试的重点在 "单元",什么是 "单元"

"单元"人为规定最小测试范围

不同的人、不同的组织、不同类型的项目,对 "单元" 的规定各有差别,有的认为,它的范围在一个方法或函数内。有的认为,一个类或一个接口也算是一个单元。

吐槽
我讨厌 单元测试,它的 "单元" 概念宽泛、指向不明确、边界模糊,我的 "单元" 不等于你的 "单元"

我感觉 单元测试 更多的是在玩概念,最可气的是,几乎所有的互联网公司还都在谈 单元测试,而且谈的还各不相同

以下这些测试类型,或有明确的指向,或有的清晰边界,不会让人产生不同的理解,我称为"好的"测试类型

  • 功能测试 : 边界清晰,测试范围为某一具体功能
  • 接口测试 :边界清晰,测试范围为某一具体接口
  • 回归测试 :指向明确,每次代码改动后,都需要对以前测试通过的代码重新测试,防止现有代码对原有逻辑产生影响
  • 压力测试 :指向明确,对接口或页面等地方进行抗压测试
  • 界面(UI)测试 : 指向明确,对产品进行用户使用层面的测试

以下这些测试类型,或指向不明确,或有边界模糊,或让不同的人有不同的解释,我称为"坏的"测试类型

  • 单元测试
  • 集成测试
  • 系统测试

ps : 不必太认真,看个乐就行

单元测试的要求

虽然大家对单元边界的定义各不相同,但对单元测试要的一些要求,却是一致的

  • 自动化 单元测试必须自动执行、自动验证,不能需要人工交互。单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。

  • 相互独立 单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。例如:method2 需要依赖 method1 的执行,将执行结果做为 method2 的输入

  • 轻量 单元测试必须是轻量的,不能依赖网络、其他服务、中间件、数据库、文件、IO等。上千个单元测试一般在 10 秒以内跑完

  • 可重复执行 单元测试通常会被放到持续集成中,每次有代码 check in 时单元测试都会被执行,因此需要重复执行,并且要求,上次的执行不会对下次执行产生影响。

  • 可维护 单元测试也是代码,也需要维护,一般是作为项目代码的一部分,但是单元测试代码不能和业务代码混在一起,必须分隔开

代码覆盖率

单元测试的一个重要指标就是看代码覆盖率。但有些公司可能陷入了代码覆盖率的陷阱,认为覆盖率越高越好,其实不然,即使达到 100% 的代码覆盖率,也只能说明你的各个单元没有问题,并不能保证你的功能没有问题。

所以对于代码覆盖率,建议是核心代码全覆盖,其他代码,根据自己的项目资源,选择性覆盖。

以上是针对整个项目来说,对于单个被测单元来说,要做到全路径覆盖,即要覆盖所有的判断条件已经异常情况。

什么时候进行单元测试

最好的时机是写业务代码之前先写好测试(这是 TDD 所推崇的)
其次是跟业务代码一起写,一块业务逻辑写完后,立刻写几个单元测试验证
最后是等业务代码编写完成之后再开始补单元测试

单元测试的难点以及解决办法

难点1:如何确定单元的范围。范围过大就变成了集成测试,范围太小又会增加工作量。
解决办法:可以根据自己的项目情况,多多参考行业其他项目的粒度划分,合理的粒度设置是单元测试的基础。以常见的 web 项目为例,大多以单个方法为单元,当然这也要结合你自己的项目情况。

难点2: 单元中有依赖关系。假如一个项目,以方法为单元,某个方法依赖另一个方法,怎么对该方法进行单元测试
解决办法mock依赖。我们在对该方法进行单元测试时,可以先假设它所依赖的方法是可信的,然后我们 mock 它所依赖的方法,让它所依赖的方法不会出错,然后就可以进行测试了。单元测试只需要保证单元内的业务逻辑是正确的,不需要保证单元的依赖是正确的。

难点3: 难以进行单元测试。上面说到单元中有依赖关系可以通过 mock依赖 解决,但是如果一个单元依赖项过多,既需要依赖外部服务、又需要依赖数据库、中间件、io等,会导致单元测试难以编写。(这也是大多数项目进行单元测试时面临的挑战)
解决办法:重构。如果你发现项目代码难以进行单元测试,说明你的代码设计有问题,可测性不够好,这就需要对代码进行重构了。

番外

代码级测试常用方法

自动静态方法能够以极低的成本发现以下问题,是提高代码质量的好方法:

  • 使用未初始化的变量;
  • 变量在使用前未定义;
  • 变量声明了但未使用;
  • 变量类型不匹配;
  • 部分内存泄漏的问题;
  • 空指针引用;
  • 缓冲区溢出;
  • 数组越界;
  • 不可达的僵尸代码;
  • 过高的代码复杂度;
  • 死循环;
  • 大量的重复代码块;
  • ...

实现方式:企业结合自己的编码规范定制度规程库,并于本地的 IDE 开发环境和持续集成流水线整合 代码本地开发阶段,IDE 环境就可以自动对代码实现自动静态检查;当代码提交到仓库后,CI/CD 流水线自动触发代码静态检查,如果检查到潜在错误,就会自动发邮件通知代码递交者。

个人对单元测试的看法

  • 单元越早开始进行,越容易实施,拖的越久越难,最佳时机是在项目开始时
  • 单元测试是有成本的,需要编写代码、维护和管理代码
  • 单元测试是跟着业务需求走的,当业务需求发生变化,不仅要更改业务代码,也需要同步更改测试代码
  • 单元测试不是提高代码质量的唯一办法,统一项目编程风格、制定项目代码规范、使用静态代码扫描工具,这些都是低成本高收益,效果立竿见影的好方法,实施优先级应高于单元测试
  • 单元测试不应掉入代码覆盖率的陷阱,100% 的代码覆盖率只能保证你的单元正常,无法保证你的功能正常,没有必要一味追求高代码覆盖率

参考

从头到脚说单测 —— 谈有效的单元测试
Spring Boot Test (一、快速入门)
小谈 UT 随行付微服务测试之单元测试
一文帮你理解什么是单元测试 单元测试常见问题及测试方法