当我们运行了测试用例,发现其中一些测试用例未能通过。ok,这下要修复测试用例了!但是,到底需要修复哪些内容呢?
其实从用例被加载到最终执行的过程中,有很多因素可能导致测试失败:
在测试构建过程中出现了问题
如果测试通过pipeline运行的,那么可能是pipeline出现了问题。
该代码正在运行的环境中存在问题
测试代码运行的环境中存在问题
被测代码存在缺陷
测试代码中的错误
测试应该测试的内容的错误
可以说,在最后三个例子中描述了一次失败的测试,该测试成功地发现了问题。但在前四个例子中,我们甚至没有执行到用例这一步,问题阻碍了测试的进行。因此,在这些情况下,需要修复的不是测试本身。
这就剩下名单上的最后三个了。如果测试发现了代码中的缺陷,那就太好了!修复这个缺陷。如果测试中有编码错误,那就太好了!修复这个错误。但如果测试没有测试它应该测试的内容呢?如果测试设计有问题呢?很简单,修复测试!
修复测试的挑战
理想情况下,事情确实会这样发生。你发现了失败的测试。你理解了它存在的原因以及它应该测试的内容。因此,你根据自己的理解对测试进行了更新,并使其再次通过。也许你还会review和其相关的测试,并对它们也进行更改,你甚至可能还会添加一些额外的测试。
可惜的是,事情往往不是这样的。尤其是当测试随机失败时。
你以为已经完成了。你编写了代码,进行了一些探索性测试,编写了测试用例,它们通过了。然后,要么是在你的本地机器上,要么是在pipeline中,你运行了所有的测试,但其中一些却失败了。你原本以为已经完成用例,但事实证明你还没有。
在那个时候,人们很容易被失败的测试所吸引,并简单地修复导致失败的断言。你查看测试运行器报告的实际结果,心想“是的,看起来是对的”,然后更新测试,以实际结果为依据进行断言。通常情况下,这样做是没有问题的。这是处理此类问题的正确方法。
但有时候情况并非如此。测试会失去一些东西:一些覆盖面,一些意图的清晰度。测试会失去一些价值。如果这种情况发生几次,那么这个测试就不再是一个很好的测试了。它仍然提供了足够的价值,以至于你不想将其删除,但你也不太清楚这个测试究竟在测试什么,以及它与其他相关测试如何共同提供对被测试对象的充分覆盖。
最终该测试成为历史遗留测试。
遗留测试的陷阱
遗留测试是遗留代码的一个子集:你无法废弃的代码,但又希望它能有所不同。
“历史遗留代码通常被定义为“比编写它的团队做出更多设计决策的代码”。
转换为遗留测试:现有的测试正在比团队做出更多的关于测试什么以及如何测试的决定。
那么,你该如何避免陷入遗留测试的陷阱呢?这里有三个建议。
“测试即代码”和“测试即测试”
首先,区分“测试即代码”和“测试即测试”是有帮助的。当你把测试看作“测试即测试”时,你考虑的是测试能够给你的被测应用程序提供的信息。当你把测试看作“测试即代码”时,你考虑的是如何让测试代码按照你的要求运行。
尤其是当测试随机失败时,人们往往只把它当作代码来看待。你需要对测试代码做哪些修改才能让它通过?这时就该退后一步,从另一个角度来看待这个测试:这个测试应该提供哪些信息?我该如何确保它能持续提供这些信息?
明确目的
当你的测试清楚地表达了它们的意图时,提出这个问题就容易多了。每个测试究竟在测试什么?可以通过给测试起清晰的名字、将设置和清理代码与实际测试分离以及有意识地将测试分组来实现这一点。这可能还意味着在代码中稍微“笨拙”一些,使代码尽可能易于阅读——即使这意味着牺牲一些可维护性。
持续讨论
最后但同样重要的是,你需要在团队内部持续进行关于测试策略的讨论。你想测试什么、为什么、在哪里以及如何测试?尽管你想让你的测试代码尽可能具有表达性,但这不能仅仅通过代码来实现。你需要进行讨论,因为代码永远无法完全表达自己。正如Peter Naur在1985年的文章《Programming as Theory Building》中所写:
在Ryle’s notion of theory的观点中,程序员必须构建的是关于世界中某些事务将如何由计算机程序支持的理论。在编程的理论构建观点中,程序员构建的理论在其他产品(如程序文本、用户文档和附加文档,如规格)之上具有优先地位。
要真正理解测试,唯一的方法就是进行关于测试的讨论。这样,当测试应该测试的内容出现错误时,你不仅要修复代码测试,还要修复测试本身。
下方扫码关注 软件质量保障,与质量君一起学习成长、共同进步,做一个职场最贵Tester!
往期推荐