杂谈单元测试
目录
杂谈单元测试的价值、困境、误区、原则、指南与原子正确性。
单元测试概念
具体来说单元测试到底是什么?
单元测试是一种软件测试方法,通过编写代码来验证应用程序中最小可测试单元
(如单个函数、方法或类)的正确性。通常,单元测试由开发人员在功能实现过程中或完成后编写,其目的是确保每个最小可测试单元
都能按照设计预期正常工作。
单元测试关键概念:
- 独立性。不依赖其他测试或外部资源。
- 自动化。开发过程中会频繁执行,目前自动执行检测。
- 可重复性。可重复,且每次运行结果应该一致,除非代码有改动。
- 覆盖率。单测的一个目标是尽可能的覆盖代码路径。
- 断言。单测需要有明确的断言。没有断言的单测是伪单测。
单元测试的价值与必要性
我们都知道单元测试的重要性,那单元测试有哪些价值呢?
-
提高代码质量。
质量涵盖正确性、可靠性、稳定性、可维护性。甚至单测可以反向要求代码满足设计原则,比如开闭、单一职责、里氏替换、依赖倒置、合成复用、接口隔离、迪米特原则。
-
提高效率。
看似增加了开发成本,但在开发期间就能节省大量调试时间,且长期来看,减少了回归成本,我们也会尽量去减少单测成本。
-
便于重构和维护。
单元测试会迫使代码做更好的分层,且代码原子化缩小影响范围,便于重构维护。
-
样例使用手册。
有助于他人及自己更具体地理解代码与预期行为,而不需要带着抽象的代码在脑海里想象。
-
增强信心。
单测有助于开发人员更自信地进行代码修改和重构。
原子正确性与单元测试
一个函数或方法肯定没有问题,也就是函数提供的能力正如函数签名(包含入参与出参)说表达的,这种能力,我们可以称为原子正确性。
函数/方法形如细胞,相同的细胞组合成组织,不同的组织构成成器官,器官组合成生命体。单测的目标是确保重要细胞是健康的,从下往上可以正确健康地组合成组织、器官,直至生命体。
单测可以让问题反馈路径缩短,从 AI 辅助编码->人工确认->修改代码->运行单测->确认结果->发现问题->AI 辅助编码修复,周而复始,都在本地开发阶段完成。
避免将问题,比如边界、异常、兼容性等遗留到测试阶段,带来 bug 记录、复现、排查分析、沟通等成本。
这篇文章关于原子正确性与单测讲述的很好:一定要写单元测试!。
哪些代码需要单元测试
代码是否有分类,不论新旧项目,是否能从单元测试收益与成本角度初略划分出代码类别?
-
算法类代码。
大部分属于重要基石代码,与业务逻辑耦合度低,扇入大,即模块化且可复用性高。单测收益高,且由于代码原子性与稳定性,单测成本低。
-
业务调度类代码。
与业务逻辑耦合度高,扇入小,即模块化与可复用性低。单测收益低,且由于代码复杂性,单测成本高。但适合进行集成与接口测试。
-
琐碎代码。
衔接代码或零散一两行代码。不需要考虑单测。
-
复杂代码。
可能是历史技术债务,可能包含大量兼容性代码,可能耦合性高直接依赖外部因素。估计都不好写单测,反而需要重构才能写好单测,但重构的成本更高,收益低。什么时候考虑重构,再根据实际情况考虑。
依赖程度越高,测试成本越大。
单元测试面临的困难
对于一些语言和环境来说单元测试存在一些门槛,比如:
- 环境与配置复杂,门槛高,记不住,不稳定。目标:开箱即用。
- 测试用例选择困难。目标:善用 AI 辅助生成测试用例。
- 单元依赖外部因素。目标:善用 stub、mock 解除依赖。
- 回报不足。目标:认识到单测的投入可以尽量避免测试阶段陷入 bug 的狂轰乱炸与慌乱状态,以及仓促修复导致代码资源组织不善。
遗留代码及其优化解决方式
-
较老的技术栈或框架
- 数据库老旧。
- 开发语言版本低。
- 框架引擎版本低。
-
兼容老旧功能的代码
- 不好分辨是否要兼容。
-
缺乏文档与单元测试的代码
- 接口代码与文档割裂,未能自动化。
- 单元测试缺失,单元测试边界不清。
-
优化解决遗留代码的方式
- 推翻重来。人力和时间不允许。如果允许的话考虑愚公移山的方式。
- 代码重构。在每次迭代里,可以基于较为模块化的代码进行重构,避免遗漏。
- 补充单元测试。新的迭代中一旦涉及到旧方法函数,考虑对其补充单元测试。对于旧项目中新增模块化代码,必须选择性单测。
单元测试误区
- 1、缺乏断言的假单测。
- 2、单测作为白盒测试,应该视为对方法函数签名的黑盒测试。
- 3、未解除外部上下游的依赖的单测。应该善用 mock。
- 4、单测粒度过大。单测并不是对接口或是一个业务流程的单测,单测是对最小可测试单元进行测试。一个个可靠的单元从下而上构建了一个可靠的整体。
- 5、盲目所有代码块单测。单测不需要对所有代码进行测试,选择性单测,单测有收益的同时也有成本,衡量收益与成本。对模块化的算法类、上层数据与流程服务调度协调代码重点单测。
- 6、流水式面条式的历史复杂代码不适合单测,优先考虑拆解重构,单测的前提做好代码分层与模块化实现。
单元测试遵循的原则
- 独立性。要求单测颗粒度适当,满足独立性。
- 可重复执行。不依赖于外部因素,满足可重复执行。
- 自动化。可自动化执行。
- 有明确的断言。
- 执行速度快。
- 分支等价与边界测试充分。
- 覆盖率高。
- 相同语言尽量采用相同的单元测试规范。
- 前期应用层面代码,选择性单测。
覆盖率
一般我们在准备用例时,会关心比如分支覆盖、条件覆盖等,单测完成后可以生成对应的覆盖率报告。覆盖率高才能尽可能地保证单测代码质量。
对于复杂逻辑或关键路径,也可以考虑手动检查覆盖情况。
单元测试一些基础指南
AI 实时辅助编程,85% 以上的代码都可以由 AI 工具辅助完成,工程师要做的是:任务分解、(子)任务描述、AI 工具提示词与规则设定、代码校验。
虽然 AI 工具可以也可以完成单元测试的大部分代码,也需要工程师协助补充说明用例及单测代码校验。所以我们也需要熟悉单测的一些技巧与编写方式,才能校验 AI 工具辅助的单测
- go 单测使用指南.md
- go 单测基准测试.md
- go 单测指令与参数.md(虽然 IDE 一般都隐藏在按钮下,对于实际运行指令与参数也要有个底)
- phpunit 基础使用指南.md
- phpunit 指令与参数参考.md
- phpunit.xml 配置说明.md
- phpunit 测试套件指南.md
- phpunit 与项目框架衔接指南.md
- Mockery 模拟框架指南.md
9ong@TsingChan 文章内容由 AI 辅助生成。