你在测试金字塔的哪一层?(上)
在准备将软件上线到生产环境之前需要进行测试。随着软件测试方式日趋成熟,软件开发团队的测试也在取代大量手动测试,逐渐实现自动化测试。通过自动化测试,开发团队可以在短短几分钟内就了解到软件是否存在问题,而不需要等待几天的时间。
自动化测试大大地缩短了反馈周期,与敏捷开发、持续集成和DevOps文化密切相关。本文将分为上、下篇来探讨如何构建一个高响应、可靠并且可维护的测试组合,无论是针对微服务架构、移动应用程序还是物联网生态系统。
一、自动化测试的重要性
软件已经成为我们生活中重要的组成部分。早期,软件的目的仅仅是提高企业效率,但现在它的作用远不止于此。许多公司都在努力成为一流的数字化公司。作为用户,我们每天都在使用各种各样的软件,创新的车轮转动越来越快。
要想跟上创新的脚步,我们必须在保证软件质量的同时加快交付的速度。持续交付是一种软件工程手法,通过在短周期内完成软件产品的交付过程,确保软件可以稳定、持续地发布。通过构建流水线自动化测试,自动将其部署到测试和生产环境中。
随着软件数量的不断增加,手动构建、测试和部署很快就会变得不切实际。如果我们不想把大量时间都花在重复性的手动测试上,而无法用于开发正常运行的软件,那么自动化测试是前进的必由之路。从构建到测试,从部署到基础架构,自动化测试是不可获取的。它能够提高效率、减少错误,并未开发人员释放更多时间专注于创造性的工作。
传统的软件测试通常是手工操作的,包括将应用程序部署到测试环境中,然后执行黑盒测试,如点击用户界面检查是否有任何故障。这些测试通常是由测试脚本指定,以确保测试人员进行一致的检查。
手动测试的所有变更都是耗时、重复且乏味的。重复是枯燥的,而枯燥容易导致错误的出现,还会让测试人员在一周后产生“另谋高就”的想法。幸好,有一种方法可以解决这种重复性工作:自动化测试。
自动化测试会极大程度地改变软件开发人员的工作方式。一旦将这些测试自动化,测试人员就不再需要手动执行点击操作来检查软件是否仍能正常运行。通过自动化测试,可以轻松修改代码库。如果之前在没有适当测试组合的情况下进行大规模重构,你一定会知道这是多么可怕的经历。如何确保在重构过程中避免不小心破坏任何东西?只能一个个手动执行测试用例了。如果能在喝一口咖啡的时间内,在几秒钟内知道自己是否进行了大规模修改,那该有多好。这听起来更有意思。
二、测试金字塔
在《敏捷成功》一书中,Mike Cohn提出了一个重要的概念:测试金字塔。这个概念通过视觉隐喻向我们展示了不同层次的测试。
Mike Cohn独创的测试金字塔由三层组成(从下到上):
- 单元测试
- 服务测试
- UI测试
然而,一些人对测试金字塔的命名和某些概念提出了批评,从现代观点来看,测试金字塔过于简单,这会产生误导。在实际应用中,测试的层次和比例会因项目的特殊需求而有所不同,因此,我们需要灵活地应用测试金字塔,根据具体情况进行调整和定制,以确保我们能够全面而有效地测试软件。
尽管如此,测试金字塔的本质是一种经验法则,用于构建自己的测试组合。Cohn强调在最初构建测试金字塔时要注意两点:
- 编写不同粒度的测试
- 随着测试级别的提高,应进行的测试数量会减少
坚持金字塔的形状,以构建一个健康、快速和可维护的测试组合,但不要形成“测试冰激凌锥”,因为这会导致维护困难且运行时间过长。
我们不必过于拘泥测试金字塔中每层的名称。实际上,这些名称可能会带来一些误导。例如,“服务测试”是一个难以理解术语,正如Cohn本人曾说的“我观察到很多开发人员完全忽略了这一层”。在现代的单页面应用框架(如react、angular、ember.js)中,UI测试显然不必位于金字塔的最高层,完全可以对UI进行单元测试。
考虑到原始名称的缺点,根据代码库和团队讨的需要,为测试金字塔每层选择其他名称,只要中保持一致即可。
三、注意
1、团队在测试命名上保持统一
讨论测试的不同分类总是非常困难的。当提到单元测试时,不同人对其理解存在一定差异。以集成测试为例,有人认为集成测试的覆盖面非常广,可以测试整个系统中的多个方面。有些人喜欢称之为集成测试,有些人则更喜欢称它们为组件测试,还有些人喜欢称为服务测试。很多人会争辩说,这三个术语是完全不同的东西。在这个问题上并没有绝对的对与错。迄今为止,软件开发社区也没法给出关于测试术语的明确定义。
术语含义本身有模糊性,所以不必过分纠结。无论是叫端到端测试也好、广域栈测试,还是功能性测试,都没问题。如果业界能有一些明确定义的术语并统一语言,那是再好不过了。可惜目前尚未到达这一点。此外,在编写测试时会存在许多细微差别,它们的范围更像是互相重叠而不是互相独立的,这使得保持术语的一致性更为困难。
重要的是找到适合团队的术语,并清楚理解不同类别测试之间的区别。团队需要在测试命名上保持统一,并为每一类测试明确定义范围。只要在团队内部达成一致(甚至在组织内部),就不需要过多关注其他事情了。
2、把测试放在部署流水线上
如果正在实施持续集成或者持续交付的实践,那么在每次提交更改时,将使用一个部署流水线来运行自动化测试。这个流水线通常会被分成几个阶段,逐步建立起团队对将软件部署到生产环境的信心。在考虑如何在部署流水线中放置不同类型的测试时,需要思考持续交付的核心价值观之一:快速反馈。
构建流水线的目标是在构建失败时能够及时通知测试人员。测试人员肯定不希望等待一小时后才发现最新的改动因为几个简单的单元测试而失败。实际上,如果流水线需要这么长时间才能给到反馈,测试人员可能早就已经等不及回家了。
为了快速获得但亏,我们可以将运行快速快的测试放在流水线的较早阶段执行,这样我们可以在几秒或几分钟内得到反馈。反之,将运行时间较长的测试(通常是覆盖范围更广的测试)放到流水线的后期阶段执行,以免影响我们从运行速度快的测试中获取快速反馈的体验。
部署流水线中不同阶段的差异并不是由测试类型决定的,而是取决于测试的运行速度和覆盖范围。在这种情况下,将一些覆盖范围有限、运行速度快的集成测试与单元测试放在同一个阶段是一个合理的决策。我们的目标是更快地获得反馈,而不是在各种类型的测试之间划出清晰的界线。
3、避免测试重复
我们已经了解了为什么需要为软件编写不同类型的测试,但是这还有一个需要避开的陷阱:金字塔不同层级进行重复测试。编写和维护测试需要花费时间,而阅读和理解其他人编写的测试也是如此,此外运行这些测试也要费时间。
对于产品代码,我们应该追求间接性,尽量避免重复。在实现测试金字塔时,我们应该牢记以下两个基本法则:
- 当一个更高级的测试发现了一个错误,并且底层测试都通过了,我们应该写一个低层级的测试来覆盖这个错误。
- 我们应该尽可能地将测试推进到金字塔的下层。
第一条法则是因为低层级测试能够帮助缩小错误范围,并且将大部分上下文隔离开,从而更容易重新错误。在调试当前问题时,低层级测试能够更快地运行,而且没有太多冗余的内容。此外,它们也是很好的回归测试,确保已修复的问题不会再次出现。
第二条法能保持测试组合的快速运行。如果在底层及测试中已经覆盖了所有情况,那么维护一个高层级的测试就没有必要了。因为它并不能为软件的正常工作提供更多的信心。如果有许多无效的测试,它们只会让你的日常工作变得繁琐。这样的测试组合会拖慢工作节奏,当你改变代码行为时,还需要修改更多的测试。
总而言之,如果编写更高层级的测试可能增加对软件的信心,那么就编写高层级的测试。对于一个Controller类,编写单元测试可以测试其内部的逻辑。然而,它无法告诉我们该Controller是否能够真正响应HTTP请求的REST路径。在这种情况下,我们可以将测试层级上衣,编写一个专门测试这一点的测试——只测试这一点,不需要更多。我们无需再测试所有的条件分支和边缘场景,因为底层级测试已经涵盖了这些内容。确保高层级测试仅关注底层及测试未覆盖到的部分。这样可以确保测试的焦点准确,并避免重复劳动。
对待已经失去价值的测试必须坚决将其消灭。我们需要删掉那些已经被低层级测试覆盖完全的高层级测试,因为它们不再提供额外的价值。尽可能用低层级测试来取代高层级测试。有时候,这可能会有一些困难,特别是当你知道设计测试本身就很具有挑战性时。我们要警惕沉没成本的思维陷阱,果断摁下删除键。没有理由在不再提供价值的测试上浪费宝贵时间。
四、写在最后
不管你是工作在一个微服务项目上,还是IoT设备上,抑或是手机应用或者网页应用,希望这篇文章能够为你提供帮助。下篇,我们将详细介绍测试金字塔的三个层级。