如何写出有效的单元测试?

冗余测试会提高维护成本。

避免冗余测试

避免条件逻辑

条件逻辑会让你的单元测试更难以维护,出问题不容易排查,不够精准;

避免条件逻辑

单测需要确定性

避免脆弱测试,Mock不确定的依赖:时间、随机数、并发性、基础设施、现存数据、持久化、网络等等。

单测需要确定性

测试快速执行

避免sleep等操作,导致测试执行缓慢。

测试快速执行

避免过度指定

对于过度指定的讨论,其核心问题就是要我们判断哪些是单元测试应该覆盖的,哪些是应该留给其他测试手段的。如果一个场景,单元测试覆盖之后,导致经常单测失败,需要不断更新维护,那就可以考虑不做单元测试覆盖。

像素完美是一个典型的、经常拿出来讨论的例子,Flutter的Golden Test就是一个golden master testing的例子;《有效的单元测试》中关于像素完美的讨论:

像素完美:顾名思义,是一种特定于图形和图像生成的测试坏味道。它混杂了魔法数字和基本断言,使得测试极难阅读也极其脆弱。

这种测试几乎无法阅读,因为即使测试在语义上是处于高层概念的,却仍然会针对硬编码的底层细节例如像素坐标和颜色来进行断言。指定坐标上的像素是黑还是白,与两个图形是否相连或堆叠的概念是有区别的。

这种测试极其脆弱,因为即使很小的和不相关的输入变化——是否是另一个图像,或图形对象的渲染方式——都足以影响输出、打破测试,谁让你非要精确地检查像素坐标和颜色呢。同样的问题在采用golden master技术时也会遇到,其做法是事先将图像录制下来,并手工检查其正确性,以后再进行测试时就将渲染出的图像与之进行比对。

这些可不是我们愿意去维护的测试。我们不希望带着这种脆弱的精确度去编写测试,而是使用模糊匹配和智能算法来代替繁琐的数值比较。

对于特定场景,Golden Test是一个非常有效的手段,但需要非常谨慎的评估;慎用Golden Test!

慎用Golden Test!

不要写永不失败的测试,不要写没有校验的测试

单测需要对明确的逻辑校验,永不失败的测试或者没有校验的测试是不可信赖的。

不要写永不失败的测试,不要写没有校验的测试

测试不要名不副实

避免测试的描述与测试内容不符;测试结果必须精准;测试该失败的时候一定要失败!

测试不要名不副实

测试私有或者受保护的方法

解决思路:

  • 将方法变成公共方法;
  • 将方法抽取到新类;
  • 将方法变成静态方法;
  • 将方法成为测试可见方法。

避免强制的测试顺序

依赖测试顺序导致测试可靠性变得脆弱,未来维护成本变高。

避免强制的测试顺序

清理测试环境

在teardown阶段清理测试环境,例如还原全局的Config、清理创建的文件目录等等。

清理测试环境

统一的单测命名、变量命名

统一的单测命名可以提高可读性、可维护性。

使用有意义的断言

断言的错误信息要有意义,出现问题能够明确错误的原因。

使用有意义的断言

把单元测试视为“一等公民”

测试用例应该被视为“一等公民”:同样需要代码评审,同样需要代码质量检查,确保单元测试的有效性。

单元测试代码评审的过程,也是团队同学互相学习的过程,沉淀最佳实践的过程。

加速执行速度

日常对单测执行时间进行监控,对测试进行性能分析,优化执行时间过长的测试用例。

测试金字塔

测试金字塔是Mike Cohn在他的著作《Succeeding with Agile》一书中提出了这个概念。测试金字塔是一个比喻,它告诉我们要把软件测试按照不同粒度来分组。它也告诉我们每个组应该有多少测试。

测试金字塔

为了维持金字塔形状,一个健康、快速、可维护的测试组合应该是这样的:写许多小而快的单元测试。适当写一些更粗粒度的测试,写很少高层次的端到端测试。注意不要让你的测试变成冰淇淋或者沙漏那样子,这对维护来说将是一个噩梦,并且跑一遍也需要太多时间。

测试变成冰淇淋或者沙漏

避免测试重复

在实现测试金字塔时,你也应该牢记这两条基本法则:

1、如果一个更高层级的测试发现了一个错误,并且底层测试全都通过了,那么你应该写一个低层级测试去覆盖这个错误;

2、竭尽所能把测试往金字塔下层赶。

如果你已经在低层级测试里覆盖了所有情况,那么再维护一个高层级的测试就没有必要了。警惕沉没成本的思维陷阱,果断摁下删除键。没有理由在不再提供价值的测试上浪费宝贵时间。

补充单元测试应该从哪里开始

单元测试应该及时编写,就算没有实践TDD,也应该在代码实现之后尽快编写单元测试,避免写出不可测试的代码,也可以让bug尽早暴露。

但很不幸的,我们很多时候在刚开始卓越工程,推广单元测试的时候,不得不面对补充单元测试的情况;这绝对是一个有挑战的事情。



留言