原文作者:Mike Wacker
原文地址:http://googletesting.blogspot.com/2015/04/just-say-no-to-more-end-to-end-tests.html
译者:chenhengjie123
在你生命中的某些时刻,你想起一部电影,你和你朋友都想看,但你和你的朋友都后悔看了这部电影。或者你还记得当你的团队以为他们发现了他们产品的下一个 “杀手级功能”,但上线后却是功能炸弹。
好的想法通常会在实践中失败,而在测试领域,一个听起来相当不错但实践中经常失败的概念是建立围绕端到端测试的测试策略。
测试人员可以投入他们的时间来编写任何类型的自动化测试,包括单元测试、集成测试以及端到端测试。但这个策略把大部分时间投入到端到端测试中以整体验证产品或服务。通常情况下,这些测试会模拟真实用户使用场景。
当你觉得依赖端到端测试是一个坏主意时,肯定会有人通过这个主意在理论上的合理性和存在意义来说服你。(译者注:原文是 While relying primarily on end-to-end tests is a bad idea, one could certainly convince a reasonable person that the idea makes sense in theory. 实在不知道怎么翻比较合适,麻烦大伙给下意见,我后面更正)
在开始之前,我们先来看一下 Google 的 ten things we know to be true(十件我们知道正确的事)中的第一条:“关注用户,剩下的一切都随之而来(Focus on the user and all else will follow)”。根据这条准则,关注真实用户场景的端到端测试听起来是一个很好的主意。另外,这种策略在许多地方都呈现出:
开发者喜欢它,因为它能转让大部分(如果不是全部)测试工作给别人。
管理者和决策者喜欢它,因为模拟真实用户场景的测试可以帮助他们确定一个失败用例对用户的影响有多大。
测试人员喜欢它,因为他们经常担心漏报了一个会被用户找到的 bug 或者写出一个不能验证真实场景的用例。从用户角度编写用例能避免上面两个问题同时给予测试人员更好的 “我完成测试了” 的感觉。
既然这个测试策略在理论上听起来这么赞,那么它在实践中哪里出问题了?作为示范,我基于自己收集到的我和其他测试人员的共同经历来讲述下面这个故事。在故事中,一个团队正在建立一个在线文档编辑服务(比如: Google Docs)
我们假设这个团队已经有比较不错的测试基础设施。在每个晚上:
最新版本的服务会被构建
这个版本会被部署在团队的测试环境
所有端到端测试都会在这个测试环境中进行
一个包含了测试结果的测试报告会通过电子邮件形式发给整个团队
最后期限在我们团队不断为下一个版本编写新功能的过程中很快来到。为了保证产品处于高质量水准,他们要求在功能被认定为完成前,端到端测试用例通过率至少达到 90% 。现在,离最后期限只有一天了:
剩下的天数 | 通过率 | 备注 |
---|---|---|
1 | 5% | 所有东西都有问题!登录到服务的功能有问题。几乎所有测试都需要用户登录,所以几乎所有用例都失败了 |
0 | 4% | 一个和我们服务有依赖关系的搭档团队昨天在它们的测试环境上部署了一个无法运行的版本 |
-1 | 54% | 一个开发在昨天(也许是再前一天)搞坏了保存功能。几乎一半的用例在需要保存文档。开发花了差不多一天的时间来确定这到底是一个前端的 bug 还是后端的 bug 。 |
-2 | 54% | 确定了昨天那个保存问题是前端的 bug ,开发花了半天时间来查找具体哪里出问题。 |
-3 | 54% | 一个很烂的 fix 在昨天被提交了。这个错误仍然被触发到,然后一个正确的 fix 终于在今天提交了。 |
-4 | 1% | 我们测试环境使用的实验室出现了硬件故障 |
-5 | 84% | 很多大 bug(如无法登录,无法保存)背后的小 bug 被发现了。团队还在修复小 bug 中。 |
-6 | 87% | 我们应该能达到 90% 以上,但由于某些原因,还没达到 |
-7 | 89.54% | (达到 90% 左右了,已经可以接受了)昨天没有任何 fix 被提交,所以昨天的测试达不到 90% 肯定是由于碎片造成的。 |
抛开各种问题,这个测试最终还是找到了真正的 bug 。
那么我们现在知道了端到端测试策略在实践中做得不好的地方了,我们需要改变我们的测试来避免这些问题。那么应该怎么做?
通常情况下,一个测试人员在找到一个失败的用例时,他的工作已经完成了。登记完 bug 后,接下来的修复工作就交给开发了。然而,为了确定端到端测试策略行不通的原因,我们需要跳出这个场景,并结合前面提到的第一条准则来思考。如果我们 “关注用户(剩下的一切都随之而来)”,我们必须自问一下:一个测试用例的失败到底能为用户带来多大的利益。下面是答案:
虽然这个结论看起来会让人很惊讶,但这是事实。如果一个产品能用,那么无论测试结果说明说它能否能用,它都是能用的。如果一个产品用不了,那么无论测试结果说明说它用不用得了,它都用不了。所以,一个失败的测试用例并不能直接给用户带来利益。那么什么能给用户带来利益呢?
用户只会对不正确的行为 —— bug —— 消失时感到高兴。显然,为了修复一个 bug ,你必须知道它的存在。为了知道 bug 的存在,理想地你会有执行内部测试来发现 bug(因为如果测试没找到 bug ,那么用户就会找到)。但在整个过程中,从一个失败的测试里面找 bug 来修复,它的价值只会在最后一步体现出来。
阶段 | 测试用例失败 | 开 bug | 修复 bug |
---|---|---|---|
是否产生价值 | No | No | Yes |
因此,在评估一个测试策略的价值时,你不能只看它如何找到 bug ,你还需要评估它怎么让开发去 fix(甚至避免产生)这些 bug 。
测试需要建立起一个反馈闭环,通知开发人员这个产品是否能用。理想的反馈闭环有下面这些特征:
它很快。没有开发者希望等待几个小时甚至几天才能知道自己的变更是否正常工作。有时候这个变更并不起做作用 —— 毕竟没有人是完美的 —— 而反馈闭环需要进行数次才能完成。一个快速一点的反馈闭环会引导向更快速的修复。如果这个闭环足够快,开发甚至可以在提交变更前执行测试。
它是可靠的。没有开发人员想花费数个小时来调试一个不稳定的测试( 译者注:flaky test,这个找不到合适的词来翻译,就理解为不稳定的测试好了 )。不稳定的测试让开发人员不再信任测试结果,所以最终即使它真的找到产品问题,它的测试结果也很有可能会被忽略。
它能隔离故障。为了修复一个 bug ,开发人员需要找到导致这个 bug 产生的那数行代码。当一个产品含有上百万行代码时,任何地方都有可能存在 bug ,找这些导致某个特定 bug 的代码无异于大海捞针。
那么我们可以造出这个理想的反馈闭环吗?可以的,但要从小处着手。
单元测试只取产品中的一小部分并在隔离环境下对它进行测试。它们更合适用于建立这个理想的反馈闭环:
单元测试都很快。我们只需要构建一个小的单元来执行测试,同时这个测试也趋向于小型化。实际上,十分之一秒的执行时间对于单元测试来说都是很慢的了。
单元测试都是可靠的。 简单的系统和小的单元遭受到碎片的影响要少得多。此外,单元测试的最佳实践 —— 完全隔离的测试 —— 会完全移除掉碎片的影响。
单元测试能隔离故障。即使产品有上百万行代码,如果一个单元测试失败了,你只需要查找单元测试的被测单元就能找到 bug 了。
编写有效的单元测试需要依赖管理、moking、以及隔离测试领域的技术。我不会在这里详细讲述这些技术,但作为一个引子,一个通用的适用于新的 Google 员工(或者非 Google 员工)的例子是 Google 如何构建和测试一个秒表程序。
使用端到端测试,你需要等待:首先构建整个产品,然后把它部署到环境中,最后才是执行测试。当测试执行时,不稳定的测试将由于碎片存在而一直存在。而且即使这次测试找到了一个 bug ,这个产生 bug 的代码也有可能存在于产品的任何一个地方。
虽然端到端在模拟真实用户场景这方面做得更好,这个优势很快就会被端到端反馈闭环的劣势盖掉:
单元测试 | 端到端测试 | |
---|---|---|
高速度 | 好 | 差 |
高可靠性 | 好 | 差 |
故障隔离 | 好 | 差 |
模拟真实用户场景 | 差 | 好 |
单元测试有一个主要劣势:即使这些单元在隔离状态下运行的很好,你不知道他们集合到一起后是否也能运行地很好。但即使这样,你也不需要端到端测试。为了确认他们集合后的运行情况,你可以使用集成测试。一个集成测试需要数个的单元 —— 大部分情况下是 2 个 —— 然后在整体上测试他们的行为,验证他们是否能连贯地工作。
如果两个单元没有被正确地集成起来,为啥你要在抛弃够编写更小、更专注的集成测试而去编写端到端测试来查找 bug ?当你需要从更大范围进行测试时,你只需要把范围扩大一点点来验证各个单元集成后能正常工作。
即使有了单元测试和集成测试,你还需要有少量的端到端测试来整体验证这个系统。为了找到这三种测试类型的平衡,最直观的方式就是测试金字塔。这里是一个精简的、来自 2014 Google Test Automation Conference 的测试金字塔。
大量的测试都是在金字塔底部的单元测试。越往上看,你的测试范围将越来越大,但同时测试的数量(金字塔的宽度)越来越少。
作为一个开拓者(译者注:原文是 good first guess ,没找到很好的翻译,就用开拓者来代替吧), Google 建议采用 70/20/10 的划分方式:70% 单元测试,20% 集成测试,10% 端到端测试。实际比例会根据各个团队的情况有所不同,但总体上,它应该保持这个金字塔的形状。尝试避免以下的反模式:
把金字塔倒过来变成雪糕形状。团队主要依赖端到端测试,执行少量集成测试和更少的单元测试。
沙漏型。团队一开始有很多单元测试,然后在该需要使用集成测试的地方使用端到端测试。沙漏型在底部有大量单元测试,在顶部有大量端到端测试,但中间只有少量的集成测试。
就像真实世界中金字塔是最为稳定的结构,测试金字塔也应该是最稳定的测试策略。