引言
你运行了测试 (注解 1 ) — 或者你通过流水线(pipeline)运行了测试 — 然后其中有一些测试运行失败了。这正是需要修复测试的时机了!但究竟需要修复什么呢?
- 构建的问题
- 流水线的问题(如果那是测试运行的地方)
- 被测代码的运行环境存在问题
- 测试代码的运行环境存在问题
- 被测代码的一个缺陷
- 测试代码中的错误
- 测试点里的错误
可以说,最后三个原因描述了测试真正失败的情况。这时测试才确实完成了验证问题的任务。然而对于前面四个原因,我们甚至没有开始真正的测试任务。那些问题阻碍了测试的继续执行。因此,在这些情况下,需要修复的就不是测试本身。
这就只剩下列表中的最后三个原因要调查了。如果在被测代码中发现了缺陷,那就太好了!修复缺陷就行。如果是测试代码本身的错误,那也太好了!纠正测试代码错误就行。但是,如果测试没有在验证它应该测试的东西呢?如果测试用例本身就有问题怎么办?也简单,修复测试用例就行!
修复测试面临的挑战
理想情况下,这确实是会发生的事情。你发现测试失败了。你也知道它为什么存在,以及它应该测试什么。所以你根据这种理解更新了测试代码,并让它再次通过。也许你甚至也检查了它们周围相关的测试,并对这些测试也做出一些修改。你甚至可以再添加更多的测试代码。
然而,不幸的是,通常情况并非如此。特别是当测试失败的时间点不太合适的时候。
你认为你全部完成了:编写了代码,也做了一些探索性测试,写了测试,它们也通过了。然后,无论是在您的本地机器上还是在流水线中,您运行了所有的测试。然后有部分测试失败了。你以为你完成了,但事实证明你并没有。
在那时,我们仅仅看看失败的测试,并简单地修复一下失败的断言部分就完事,这样做看起来的确是很吸引人的。你检查了测试的实际运行结果,认为“是的,一切看起来都是对的”,然后用实际运行结果值修改了测试断言部分。即便使足够频繁这样修改也没关系。这的确是处理这种情况的正确方法。
但是,有的时候,这样是不行的。因为这种情况下的测试缺失了一些东西, 比如缺少某个测试覆盖面或者某个不够明确的测试意图。那么此时该测试就失去了部分价值。这种情况发生几次后,这个就不再是那么好的测试了。虽然它仍然提供了足够的价值以至于你并不想删除它,但这个测试应该需要测试什么也就不再很清晰了。同时怎样才能结合其周围相关的测试,以达到为正在测试的东西提供足够的覆盖范围的目的。
现在,该测试转变了一个遗留测试(Legacy tests)。
遗留测试的陷阱
遗留测试是遗留代码的一部分:即指的是那些您不能丢掉但期望会有所不同的代码。或者正如 Ángel Siendones Sillero 所说:遗留代码通常被定义为“更多的用来做设计决策,而不是团队用来工作的代码”。(注解 2)
对应到遗留测试:对团队而言,现有测试对测试内容和方式做出的决定更多。那么,如何避免遗留测试的这个陷阱呢?我不会声称我有完美的答案,但我确实有三个建议。
测试即代码和测试即测试
首先,区分测试即代码和测试即测试是有帮助的。当您将测试视为测试时,您想的是测试可以为您提供的有关被测代码或应用程序的信息。而当您将测试视为代码时,您正在思考如何让测试代码做您希望它该做的事情。
特别是当测试在不合适的时间点失败时,此时只把它们当作代码来对待是很吸引人的。您需要对测试代码进行哪些修改才能通过?这时候正是可以退后一步从另一个角度来检查测试:该测试应该提供哪些信息?如何确保它持续这样做?
意图的明确性
其次当你的测试清晰地体现了它们的意图时,提问(和回答)这个问题就会容易得多。每个测试到底要测什么?您通过给测试提供清晰的名称,将初始化(setup)和回收(teardown)代码与实际测试代码分开,并仔细考虑如何通过测试分组来做到这一点。虽然这也可能意味着你的代码不再那么智能,但这使得测试尽可能容易阅读—即使以牺牲部分可维护性为代价。
持续谈话
最后但同样重要的是,您需要在团队内就您的测试策略进行持续谈话。您想测试什么、为什么、在哪里、以及如何测试?即使您想让测试代码尽可能富有表现力,但您无法仅通过代码来做到这一点。您需要对话,因为代码永远无法完全表达自己。正如 Peter Naur 在他的文章《编程即理论构建》(1985 年)(注解 3) 中写道:就 Ryle 的理论概念而言,程序员必须构建的是一个关于世界某些事务如何由计算机程序处理或支持的理论。在编程即理论构建的观点看来,程序员构建的理论优先于程序文本、用户文档和规范等附加文档如技术规格等其他产品。
对测试产生真正共同理解的唯一方法是就测试进行对话。因此,当测试该测的内容出现错误时,您不仅要修复测试即代码的部分,还要修复测试即测试部分。
那么,您遇到过遗留测试吗?您注意到的标志有哪些?您处理遗留测试最喜欢的方法是什么?
注解说明:
- 这里我用开发人员倾向于使用的方式来使用“测试”这个单词:测试是一段代码。在另外一个不同的上下文,我可能会以不同的方式使用它,比如:测试是一种性能。正如维特根斯坦所说:使用就有意义。
- 公平地说,这只是引出他们的实际观点:我们可以将遗留过程定义为“做出关于团队如何运作比团队本身更多决策的过程”吗?🤔
- 我之所以发现这篇文章,是因为 Alistair Cockburn 将它列入了他的优秀著作《敏捷软件开发》的附录 B 里面。