Long Live the Test Pyramid
最近看到一篇关于测试金字塔的讨论,觉得非常有道理,原文在这里: https://www.smashingmagazine.com/2023/09/long-live-test-pyramid/
顺便翻译一下,希望对大家有所帮助。
我亲爱的同事Jan Philip Pietrczyk曾经对开发者在编写功能代码方面的责任发表过评论:
“我们的日常工作最终会交到信任我们的人手中,他们不仅信任我们已经尽力而为,还信任我们的代码能够正常运行。” — Jan Philip Pietrczyk
他的这些话一直深深地印在了我的脑海中,因为它让我们的代码置于依赖它的人们的背景之中。在这个快节奏的世界里,用户信任我们编写最优秀的代码,并且我们的软件“简单地”可以运行。确实,要达到这种信任水平是一个挑战,这就是为什么测试是任何开发堆栈中如此重要的一部分。测试过程评估了我们工作的质量,通过对不同情况进行验证,帮助在问题变得严重之前识别问题。
测试金字塔是众多测试策略之一。尽管自2012年引入以来,它可能已经成为了主要的测试模型,在过去的十年中占据主导地位,但我发现现在提及它的次数并不像过去那么多。它是否仍然是测试的“首选”方法?与此同时,许多其他方法也已经涌现,因此是否可能是测试金字塔被更适合当今开发的更现代模型所淹没和遮盖?
这就是我想要探讨的问题。
测试策略的关键点
与用户建立信任需要一个强大的测试策略,以确保我们编写的代码使产品按照他们的期望工作。我们应该从哪里开始编写好的测试?我们需要多少个测试?许多人一直在思考这个问题。但是Kent C. Dodds发表的一个简短评论让我恍然大悟。
“最大的挑战是知道要测试什么,以及如何进行测试,以获得真正的信心,而不是测试实现细节所带来的虚假信心。”
-Kent C. Dodds 这就是起点!确定测试的目标是测试策略中最关键的任务。互联网上充斥着描绘糟糕决策的迷因,其中许多是因为根本不知道特定测试的目的以及我们需要多少测试来确保信心。在测试方面,有一个“正确的比例”,以确保代码经过适当的测试并且按照预期工作。
问题在于许多开发人员只关注一种类型的测试 - 通常是单元测试覆盖率 - 而不是对各种单元如何协同工作制定策略。例如,当测试一个水槽时,我们可以分别测试水龙头和下水道,但它们是否协同工作呢?如果下水道堵塞,而水龙头继续放水,那么事情就不正常了,即使单元测试表明水龙头正常工作。
关于测试的方法通常用不同的形状来描述,就像我们已经看到金字塔模型一样。在这篇文章中,我想分享一些我观察到的形状,以及它们在现实场景中的应用,最后总结出哪种测试策略符合我对当今开发实践中良好测试覆盖的个人标准。
回顾基础知识
在此之前,让我们回顾一些不同测试类型的常见定义,以刷新我们的记忆:
手动测试
这是由实际人员执行的测试。这意味着测试将要求真实用户按照脚本化的使用案例进行点击应用程序,以及未经脚本化的尝试在不可预见的情况下“破坏”应用程序。这通常是通过与由产品团队观察的用户进行面对面或远程面试来完成的。
单元测试
这种类型的测试是将应用程序分解为小的、隔离的、可测试的部分,或者称为“单元”,通过分别和独立地测试每个单元以确保其正常运行来提供覆盖。
集成测试
这些测试关注组件或系统之间的交互。它们一起观察单元测试,以检查它们在集成在一起作为整体工作时是否良好。
端到端(E2E)测试
在这种类型的测试中,计算机模拟实际用户的交互。将E2E视为验证用户故事的一种方式:用户是否可以完成需要一系列步骤的特定任务,结果是否符合预期?这是从用户体验的一端到另一端进行测试,确保输入产生正确的输出。
那么,这些测试类型应该如何互相交互呢?测试金字塔是我们传统依赖的隐喻,用来将这些不同类型的测试整合到一个完整的测试套件中,适用于任何应用程序。
伟大的测试金字塔
测试金字塔是由Mike Cohn在他的书《成功的敏捷》中首次引入,并由Martin Fowler 在他的“实用测试金字塔”文章中进一步发展的,根据测试的性能和成本对测试进行了优先排序。它建议编写具有不同粒度级别的测试,包括较少的高级别测试以及更多快速、经济、可靠的单元测试。推荐的测试顺序是从快速和经济到慢和昂贵,从底部开始有很多单元测试,然后是服务,即中间的集成测试。然后是位于顶部的较少但更具体的UI测试,包括端到端测试。
在测试社区中,有一种越来越强烈的情感,认为测试金字塔过于简化了测试应该如何结构化的问题。马丁·福勒在关于金字塔形状的文章发布近十年后,在一篇较新的博客文章中对此进行了讨论。我的团队甚至质疑这个模型是否将我们的工作更接近最终用户或更远离最终用户。尽管金字塔的较高层增加了对个别测试的信心并提供更好的价值,但它似乎对整体如何协同工作的更大画面关注较少。测试金字塔感觉对我们来说已经不再适用,至少是在某种程度上。
从金字塔到菱形
我们团队内部讨论的一个观点是金字塔对单元测试的过分强调。金字塔是一个很好的形状,可以描述什么是单元测试以及它涵盖的范围。但如果你问四个人什么是单元测试,你可能会得到四种不同的答案。也许这个形状需要一些调整来澄清事情。
我们团队最需要澄清的是单元测试在何时何地停止。金字塔形状暗示了单元测试占据了测试过程的大部分,这对我们来说感觉不对劲。毕竟,集成测试才是将它们整合在一起的部分。
因此,我们可以将测试策略的金字塔形状演变成菱形的另一种视图:
集成测试有时被称为测试金字塔中的“被遗忘层”,因为它可能对于单元测试来说过于复杂。但在测试菱形中,它得到了更多的关注(通常分为两个具体的层次):
集成测试层
这一层基本上与我们在测试金字塔中看到的一样,但它是为那些被认为“太大而不能成为单元测试”的测试保留的,介于单元测试层和集成测试层之间。测试一个特定组件的测试是这一层的理想类型。
系统集成测试层
这一层更多地关注“真正的”集成测试,比如从API接收的数据。
因此,菱形形状暗示了一个流程,即在集成测试完成后立即进行单元测试,但对这些单个测试的强调较少。这样,集成层得到了应有的大量测试,而单元测试的重点逐渐减小。
手动测试在哪里?
无论测试策略被称为“金字塔”还是“菱形”,它仍然忽略了手动测试在过程中的关键位置。自动化测试固然有价值,但并不足以使手动测试实践变得过时。
我认为自动化测试和手动测试是相辅相成的。自动化测试应该消除例行和常见任务,使测试人员能够专注于需要更多人工关注的关键领域。自动化不应该替代手动测试,而是应该与之相辅相成。
那么,这对我们的菱形形状……或者说金字塔,有什么意义呢?手动测试在这些层次中都没有,但它应该有。自动化测试可以高效地检测错误,但手动测试仍然是必要的,以确保更全面的测试方法,提供全面的覆盖。也就是说,理想的测试策略仍然会将大部分重点放在自动化测试上。
这意味着测试策略看起来更像是一个冰淇淋筒,而不是金字塔或菱形。
实际上,这是一种真正的方法,被称为“冰淇淋筒”方法。尽管这种方法需要更长的时间来实施,但它可以提供更高的信心水平和更多的错误检测。Saeed Gatson在一篇可以追溯到2015年的文章中提供了对它的简明描述。
但是,披萨形状是否足以充分描述测试的全面性质呢?Gleb Bahmutov将这个概念推向了极端,他称之为“测试螃蟹”模型。这种方法涉及截图比较,然后由人工验证差异。Bahmutov将视觉和功能测试视为“螃蟹的身体”,所有其他类型的测试都是“螃蟹的四肢”。事实上,确实有工具在测试过程中提供前后快照,当这些快照叠加在一起时,可以突出显示视觉回归。
测试奖杯
所有测试方法都是昂贵的,而测试金字塔正确指出了这一点。只是金字塔的形状本身可能不太现实或有效地考虑了测试的全面性质以及每个测试层的重点。因此,我们需要在所有这些方法之间找到一种妥协,准确地描述各种测试层次以及每个测试层次应该受到多少重视。
我喜欢Guillermo Rauch在2016年简洁地总结了这一观点:
编写测试 不要太多 主要是集成测试
让我们更详细地分解一下。
编写测试
不仅因为它建立了信任,而且因为它在维护方面节省了时间。
不要太多
百分之百的覆盖率听起来不错,但并不总是好的。如果每个应用程序的每个细节都被测试覆盖,那意味着至少其中一些测试对最终用户体验并不关键,它们仅仅是为了运行而运行,增加了维护的开销。
主要是集成测试
这里强调了集成测试。它们具有最高的商业价值,因为它们在保持合理的执行时间的同时提供了高度的信心。 如果你花了一些时间关注Kent C. Dodds的工作,你可能会认出以下想法。他的“测试奖杯”方法将集成测试提升到比传统测试金字塔更高的优先级,与Guillermo Rauch的说法完全一致。
Kent讨论并解释了全面测试在产品成功中所扮演的重要角色。他强调了集成测试在测试单个单元之上的价值,因为它更好地理解了产品的核心功能和受尊重的行为。他还建议减少模拟测试,更多地采用集成测试。测试奖杯是一种以稍微不同的方式描绘测试粒度的隐喻,将测试分为以下类型:
- 静态分析:这些测试通过执行调试步骤来快速识别拼写错误和类型错误。
- 单元测试:奖杯对它们的强调较少,不如测试金字塔那么重要。
- 集成测试:奖杯对它们的强调最大。
- 用户界面(UI):这些包括端到端和视觉测试,在奖杯中保持了重要角色,就像在金字塔中一样。
“测试奖杯”将用户的角度置于优先位置,并拥有有利的成本效益比。这是我们的首选吗?这种测试策略是最明智的,但有一个问题。虽然单元测试仍然提供了有价值的好处,但集成测试和端到端测试存在一些缺点,包括较长的运行时间和较低的可靠性。单元测试的好处是有效的,我仍然更愿意使用它们。
那么,测试金字塔已经过时了吗?
测试金字塔仍然是一个流行的软件开发测试模型,有助于确保应用程序正确运行。然而,就像任何模型一样,它也有其缺点。其中最大的挑战之一是定义什么构成了一个单元测试。
我的团队在我们的测试流程中实施了修改后的菱形形状。我们发现这个模型并不完全错误,只是不完整。我们仍然从中获得有价值的见解,特别是在优先考虑运行不同类型的测试时。
在我看来,开发团队很少坚持纯粹的测试模式,正如Justin Searls所总结的那样:
人们喜欢辩论要编写哪种类型的测试的百分比,但这是一种分散注意力的做法。几乎没有团队编写具有清晰界限、运行迅速可靠且仅在有用的情况下失败的表达性测试。相反,专注于这一点。
这对我的团队经验也是如此,因为划分和定义测试通常是困难的。但这并不是坏事。甚至Martin Fowler也强调了不同测试模型对我们如何共同看待测试覆盖率产生的积极影响。
因此,我绝不认为测试金字塔已经过时。我甚至可能会认为,现在了解它同样重要。但关键不是过于纠结于其形状或任何其他形状。最重要的是记住,测试应该快速而可靠,并且仅在出现真正问题时才失败。它们应该有益于用户,而不仅仅是追求完全覆盖。通过在测试设计中优先考虑这些方面,您已经完成了最重要的事情。