编译:TesterHome
作者:Adam Bender,Google 工程师
(以下为作者观点)
测试一直是编程的一部分。事实上,当你第一次编写计算机程序时,几乎肯定会抛出一些样本数据,看看它的表现是否符合你的预期。自 21 世纪初以来,为了应对现代软件系统的规模和复杂性,软件行业的测试方法已经发生了巨大的变化,这一演变的核心是开发者驱动的自动化测试。
大家都知道,越在开发周期的后期发现错误,付出的成本也越高,在许多情况下成本甚至是成倍增长的。所以,能够快速迭代的公司可以更迅速地适应不断变化的技术、市场条件和客户的口味。
在谷歌,虽然我们已经走过了很长的路,但我们仍然面临着让我们的流程在整个公司可靠地扩展的困难问题。本文分享一些我们所学到的东西,希望对大家有所帮助。
为了更好理解如何从测试中获得最大的收益,让我们从头讲起。当我们谈论自动化测试时,我们真正谈论的是什么?
最简单的测试是这样定义的:
当执行测试时,把输入传给系统并验证输出,判断系统的行为是否符合你的期望。总的来说,成百上千的简单测试(通常称为测试套件)可以告诉你整个产品在多大程度上符合其预期的设计。
除了使公司能够快速开发产品之外,测试对于确保产品和服务的安全也变得至关重要。在谷歌,我们已经确定,测试不能是事后的想法。注重质量和测试是我们工作的一部分。因此,我们已将测试纳入我们工程文化的核心。
在谷歌的早期,工程师驱动的测试通常被认为是不重要的。有几个系统进行了大规模的集成测试,但大多数情况下,聊胜于无。有一个产品似乎受到了最严重的影响:它被称为谷歌网络服务器,也被称为 GWS。
GWS 是负责为谷歌搜索查询提供服务的网络服务器,它对谷歌搜索的重要性就像空中交通管制对机场的重要性一样。早在 2005 年,随着项目规模和复杂性的扩大,生产力已经急剧下降。发布的版本越来越多,而且推送的时间也越来越长。团队成员在对服务进行修改时信心不足,往往是在功能停止工作时才发现有问题。(有一次,超过 80% 的生产推送包含了影响用户的错误,不得不回滚)。
为了解决这些问题,GWS 的技术负责人(TL)决定制定一项由工程师驱动的自动测试政策。作为这个政策的一部分,所有新的代码修改都需要包括测试,而且这些测试将被持续运行。
在实施这一政策的一年内,紧急推送的数量减少了一半。尽管该项目每季度都有创纪录的新变化,但还是出现了这种下降。即使面对前所未有的增长和变化,测试也给谷歌最关键的项目之一带来了新的生产力和信心。今天,GWS 有数以万计的测试,几乎每天都在发布,客户可见的失败相对较少。
GWS 的变化标志着谷歌测试文化的一个分水岭,因为公司其他部门的团队看到了测试的好处,并开始采用类似的策略。
GWS 的经验告诉我们的一个重要启示是:你不能仅仅依靠程序员的能力来避免产品缺陷。即使每个工程师只是偶尔写出一些缺陷,当你有足够多的人在同一个项目上工作时,你也会被不断增长的缺陷清单所淹没。想象一下,一个假设的 100 人的团队,其工程师非常优秀,他们每个人每月只写一个 bug。而这群了不起的工程师在每个工作日仍然会产生 5 个新的 bug。更糟糕的是,在一个复杂的系统中,修复一个 bug 往往会引起另一个 bug,因为工程师们会适应已知的 bug 并围绕它们编写代码。
最好的团队会想办法将其成员的集体智慧转化为整个团队的利益,这正是自动化测试的作用。在团队中的一个工程师写了一个测试后,它被添加到其他人可用的公共资源池中。团队中的每个人现在都可以运行这个测试,当它检测到一个问题时,就会受益,这也是 GWS 能够扭转其命运的根本原因。
对于来自没有强大测试文化的组织的开发人员来说,把写测试作为提高生产力和速度的手段的想法可能看起来是对立的。毕竟,编写测试的行为可能比实现一个功能所需的时间还要长(甚至更长!)。相反,在谷歌,我们发现投资于软件测试对开发人员的生产力有几个关键的好处:
- 更少的调试
正如期望的那样,经过测试的代码在提交时有较少的缺陷。在谷歌,一段代码在其生命周期内预计会被修改几十次。一次写好的测试会继续带来红利,并在项目的生命周期中防止大的 bug 和恼人的调试过程。
- 增加对变化的信心
所有的软件都会改变。拥有良好测试的团队可以自信地审查和接受他们项目的变化,因为他们项目的所有重要行为都被持续验证。这样的项目鼓励重构,在保留现有行为的情况下,重构代码的变化应该(最好)不需要对现有的测试进行修改。
- 改进文档
软件文档是 “臭名昭著” 的不可靠。从过时的需求到缺失的边缘案例,文档与代码的关系很脆弱,这非常常见,而只有在注意保持测试的清晰和简洁的情况下,测试才能作为文档发挥最佳效果。
- 更简单的审查
在谷歌,所有的代码在提交之前都要经过至少一名其他工程师的审查。如果代码审查包括彻底的测试,证明代码的正确性、边缘情况和错误情况,那么代码审查员花在验证代码是否符合预期的精力就会减少。
- 深思熟虑的设计
为新代码编写测试是锻炼代码本身的 API 设计的一种实用手段。设计良好的代码应该是模块化的,避免紧密耦合,并专注于特定的责任。尽早修复设计问题往往意味着以后的返工更少。
- 快速、高质量地发布
有了稳定的自动化测试套件,团队可以放心地发布新版本的应用程序。谷歌的许多项目每天都会向生产部门发布一个新的版本,即使是有数百名工程师的大型项目,每天都会提交成千上万的代码修改。如果没有自动化测试,这是不可能的。
今天,谷歌以巨大的规模运作,随着我们代码库的增长,我们学到了很多关于如何设计和执行测试套件的方法,当然很多都是通过犯错总结出来的经验。
我们很早就学到的一个经验是,工程师们喜欢写更大的、系统规模的测试,但往往也比小的测试更慢,更不可靠,更难调试。
工程师们厌倦了调试系统规模的测试,问自己:"为什么我们不能一次只测试一台服务器?"或者,"为什么我们需要一次测试整个服务器?我们可以单独测试较小的模块"。最终,减少痛苦的愿望导致团队开发出越来越小的测试。
这导致了公司内部对 "小 "的确切含义有过大量的讨论。“小” 是指单元测试吗?那集成测试是什么规模?
我们得出的结论是,每个测试案例都有两个不同的维度:规模和范围。规模是指运行一个测试用例所需的资源:如内存、进程和时间;范围指的是我们要验证的具体代码路径。请注意,执行一行代码与验证它是否按预期工作是不同的,大小和范围是相互关联但不同的概念。
测试规模
在谷歌,我们把每一个测试都归类为一个规模,并鼓励工程师总是为一个给定的功能编写尽可能小的测试。一个测试的大小不是由它的代码行数决定的,而是由它的运行方式,它被允许做什么,以及它消耗多少资源决定的。
简而言之,小型测试在单个进程中运行,中型测试在单个机器上运行,而大型测试则在任何地方运行,如下图:
我们做出这样的区分,而不是按照传统的 "单元 "或 "集成",是因为不管测试范围如何,我们希望测试套件能够发挥的最重要的作用是:速度和确定性。
- 小型测试
小型测试是三种测试规模中最受限制的。 主要的约束是,小型测试必须在一个进程中运行,甚至必须在一个单线程上运行。这意味着执行测试的代码必须与被测试的代码在同一进程中运行。这也意味着如果数据库作为你测试的一部分的话,不能运行第三方程序。
对小型测试的其他重要限制是,他们不允许进行任何其他阻塞调用。这意味着,小型测试不允许访问网络或磁盘。
这些限制的目的是确保小型测试没有机会接触到测试缓慢或不确定性的影响因素。这些限制一开始可能看起来很过分,但是考虑到一个由几百个小测试案例组成的一定规模的套件在一天内运行,如果其中有几个非确定性地失败(通常被称为易碎测试),光去追踪原因就会成为生产力的严重消耗。以谷歌的规模,这样的问题可能会使我们的测试基础设施陷入停顿。
在谷歌,我们鼓励工程师尽可能地编写小型测试,不管测试的范围如何,因为这样可以使整个测试套件快速可靠地运行。
- 中型测试
对于许多有趣的测试来说,对小型测试的限制可能过于严格。 测试规模的下一个阶梯是中型测试。中型测试可以跨越多个进程,使用线程,并且可以进行阻塞性调用,包括网络调用,到 localhost。唯一限制是,中型测试不允许对 localhost 以外的任何系统进行网络调用。换句话说,测试必须在一台机器内进行。
运行多个进程的能力开辟了很多可能性。例如,你可以运行一个数据库实例,以验证你正在测试的代码在一个更现实的环境中正确集成。或者你可以测试网络用户界面和服务器代码的组合。网络应用程序的测试通常涉及像 WebDriver 这样的工具,它可以启动一个真实的浏览器,并通过测试过程远程控制它。
不幸的是,随着灵活性的增加,测试变得缓慢和不确定的可能性也在增加。跨进程的测试或被允许进行阻塞调用的测试依赖于操作系统和第三方进程的快速和确定性,这在一般情况下我们无法保证。中等测试仍然通过防止通过网络访问远程机器来提供一些保护,而网络是大多数系统中速度慢和非确定性的最大来源。尽管如此,在编写中型测试时,"安全 "是不存在的,工程师需要更加小心。
- 大型测试
最后,来讲讲大型测试。大型测试取消了对中型测试的本地主机限制,允许测试和被测系统跨越多台机器。例如,测试可能针对远程集群中的系统运行。
和以前一样,灵活性的增加伴随着风险的增加。与在单台机器上运行相比,必须处理一个跨越多台机器和连接它们的网络的系统,速度慢,而且不确定性大大增加。
我们大多时候将大型测试用在全系统的端到端测试,这些测试更多的是验证配置而不是代码片断,以及测试那些不可能使用测试替身的遗留组件。谷歌的团队经常将他们的大型测试与小型或中型测试隔离,只在构建和发布过程中运行,以免影响开发人员的工作流程。
- 所有测试规模的共同属性
所有的测试都应该努力做到密封性:一个测试应该包含所有必要的信息来设置、执行和关闭其环境。测试应该尽可能少地假设外部环境,如测试的运行顺序。例如,他们不应该依赖共享数据库。这种限制在大型测试中非常困难,但仍应努力确保隔离。
一个测试应该只包含行使有关行为所需的信息。保持测试的清晰和简单,有助于审查者验证代码,清晰的代码也有助于在失败时诊断故障。因为没有对测试本身的测试,所以需要人工审查来检查其正确性。作为一个推论,我们也强烈不鼓励在测试中使用控制流语句,如条件语句和循环语句。更复杂的测试流程有可能包含 bug 本身,并使确定测试失败的原因更加困难。
请记住,测试通常只有在发生故障时才会被重新审视。当你被要求修复一个你以前从未见过的破损的测试时,你会感谢有人花时间让它变得容易理解。代码被阅读的次数远远多于被写的次数,所以要确保你写的测试是你想读的。
实践中的测试规模
有了测试规模的精确定义,我们就可以创建工具来强制执行它们。强制执行使我们能够扩展我们的测试套件,并仍然对速度、资源利用和稳定性做出一定的保证。
在谷歌,这些定义的执行程度因语言而异。例如,我们使用一个自定义的安全管理器来运行所有的 Java 测试,如果它们试图做一些被禁止的事情,如建立网络连接,就会导致所有被标记为 “小” 的测试失败。
测试范围
虽然我们在谷歌非常强调测试规模,但要考虑的另一个重要属性就是测试范围。
测试范围是指一个给定的测试有多少代码被验证。
值得注意的是,当我们谈论单元测试是狭义的范围时,我们指的是正在验证的代码,而不是正在执行的代码。一个类有许多依赖关系或它引用的其他类是很常见的,这些依赖关系在测试目标类时自然会被调用。尽管其他一些测试策略大量使用测试替身(假的或模拟的)来避免执行被测系统之外的代码,但在 Google,我们更愿意在可行的情况下保持真正的依赖关系。
狭义范围的测试往往是小的,而广义范围的测试往往是中等或大的,但这并不总是这样的。例如,有可能对一个服务器端点写一个范围广泛的测试,包括所有正常的解析、请求验证和业务逻辑,但这是小的,因为它使用双倍数来代替所有进程外的依赖,如数据库或文件系统。同样,也可以对一个单一的方法写一个范围较窄的测试,但必须是中等大小。例如,现代网络框架经常将 HTML 和 JavaScript 捆绑在一起,测试一个 UI 组件,如日期选择器,往往需要运行整个浏览器,甚至验证一个单一的代码路径。
就像我们鼓励小规模的测试一样,在谷歌,我们也鼓励工程师编写范围较小的测试。作为一个非常粗略的指导原则,我们倾向于将 80% 的测试混合为狭义的单元测试,以验证大部分的业务逻辑;15% 的中等范围的集成测试,以验证两个或多个组件之间的交互;以及 5% 的端到端测试,以验证整个系统。下图描述了我们如何以金字塔的形式来看待这个问题:
单元测试是一个很好的 “基座”,因为它们快速、稳定,并且极大地缩小了范围,减少了识别一个类或函数的所有可能行为所需的认知负荷。 此外,它们使故障诊断变得快速而无痛。需要注意的两个反模式是 "冰激凌锥 "和 "沙漏",如下图所示:
在冰激凌锥中,工程师写了许多端到端的测试,但很少有集成或单元测试。这样的测试套件往往是缓慢的、不可靠的、难以操作的。这种模式经常出现在那些以原型开始的项目中,迅速产出,从来没有停下来解决测试的 “历史问题”。
沙漏模式涉及许多端到端测试和许多单元测试,但很少有集成测试。它不像冰激凌锥那样糟糕,但它仍然导致许多端到端的测试失败,而这些失败本可以通过一套中等范围的测试更快、更容易地发现。沙漏模式发生在紧耦合使其难以孤立地实例化各个依赖关系的时候。
我们推荐的测试组合是由我们的两个主要目标决定的:工程生产力和产品信心。在开发过程的早期,倾向于单元测试能让我们快速获得高信心。在产品的开发过程中,大型测试作为理智的检查;它们不应该被看作是捕捉错误的主要方法。
当考虑你自己的组合时,你可能想要一个不同的平衡。如果你强调集成测试,你可能会发现你的测试套件需要更长的时间来运行,但在组件之间捕获更多的问题。当你强调单元测试时,你的测试套件可以很快完成,而且你会捕捉到许多常见的逻辑错误。但是,单元测试不能验证组件之间的相互作用,就像不同团队开发的两个系统之间的合同。一个好的测试套件包含不同的测试规模和范围的混合,适合当地的架构和组织的实际情况。
在指导新员工时,我们经常被问到,哪些行为或属性实际上需要被测试? 直截了当的答案是:测试所有你不想破坏的东西。换句话说,如果你想确信一个系统表现出一个特定的行为,唯一的方法就是为它写一个自动化测试。这包括所有常见的,如测试性能、行为正确性、可访问性和安全性。它还包括不太明显的属性,如测试系统如何处理故障。
我们对这一总体理念有一个名字:我们称之为 Beyoncé Rule。简而言之,你可以这么理解:"如果你喜欢它,那么就应该对它进行测试"。
The Beyoncé Rule 经常被负责对整个代码库进行修改的基础设施团队所引用。如果不相关的基础设施变化通过了你所有的测试,但仍然破坏了你的团队的产品,你就得负责修复它并增加额外的测试。
失败测试(TESTING FOR FAILURE)
一个系统必须考虑的最重要的情况之一是失败。失败是不可避免的,但与其等待失败,不如写自动测试来模拟常见的失败类型。这包括在单元测试中模拟异常或错误,在集成和端到端测试中注入远程过程调用(RPC)错误或延迟。它还可以包括使用混沌工程等技术影响真实生产网络的更大的破坏。对不利条件的可预测和可控制的反应是一个可靠系统的标志。
代码覆盖率是衡量哪些特征代码行被哪些测试所执行。如果你有 100 行代码,你的测试执行了其中的 90 行,你就有 90% 的代码覆盖率。代码覆盖率经常被认为是理解测试质量的黄金标准,但我们认为代码覆盖率只衡量一行被调用的情况,而不是结果。所以我们建议只测量小型测试的覆盖率,以避免执行大型测试时出现覆盖率膨胀。
代码覆盖率的一个更隐蔽的问题是,像其他指标一样,它很快就变成了一个单独的 KPI。对于团队来说,建立一个预期的代码覆盖率标准是很常见的,例如 80%。起初,这听起来非常合理,你肯定希望至少有这么多的覆盖率。在实践中,发生的情况是,工程师们不是把 80% 当作一个底线,而是把它当作一个上限。很快,变化就开始了,覆盖率不超过 80%。毕竟,为什么要做比指标要求更多的工作?
提高测试套件质量的一个更好的方法是思考测试的行为。你有信心你的客户所期望的一切都能正常工作吗?你觉得你有信心能抓住依赖关系中的突发变化吗?你的测试是否稳定和可靠?像这样的问题是思考测试套件的一种更全面的方式。
每个产品和团队都是不同的:有些会有难以测试的与硬件的互动,有些涉及到大量的数据集。试图用一个单一的数字来回答 "我们有足够的测试吗?"忽略了很多背景,不太可能是有用的。代码覆盖率可以提供一些对未测试代码的洞察力,但它不能替代对系统测试情况的批判性思考。
要了解测试在谷歌是如何工作的,就需要了解我们的开发环境,其中最重要的事实是,谷歌的大部分代码都保存在一个单一的、单片的仓库(monorepo)。我们运营的每个产品和服务的几乎每一行代码都全部存储在一个地方。今天,我们在存储库中有超过 20 亿行的代码。
谷歌的代码库每周经历近 2500 万行的变化。其中大约有一半是由成千上万的工程师在我们的 monorepo 工作,另一半是由我们的自动化系统以配置更新或大规模更改(Large-Scale Changes)的形式进行的。其中许多变化是由直接项目以外的人发起的,我们对工程师重用代码的能力没有很多限制。
我们代码库的开放性鼓励了一种 “文化”,让每个人都对代码库负责。这种开放性的一个好处是能够直接修复你使用的产品或服务中的错误(当然要经过批准),而不是抱怨它。这也意味着许多人将在别人拥有的代码库的一部分进行修改。
另一件让谷歌有点不同的事情是,几乎没有团队使用版本库分支。所有的修改都被提交到版本库头部,并且每个人都可以立即看到。此外,所有的软件构建都是使用我们的测试基础设施验证过的最后一次提交的变化。当一个产品或服务被构建时,几乎所有运行该产品或服务所需的依赖性也都是从源头构建的,也是从资源库的头部。谷歌通过使用 CI 系统来管理这种规模的测试。我们 CI 系统的关键组成部分之一是我们的测试自动化平台(TAP)。
无论你考虑的是我们的规模、我们的单体,还是我们提供的产品数量,谷歌的工程环境都很复杂。每周都有数百万条变化的线路,数十亿个测试用例被运行,数万个二进制文件被构建,数百个产品被更新,这说起来都很复杂!
随着代码库的增长,不可避免地需要对现有代码进行修改。如果写得不好,自动化测试会使这些修改更加困难。
脆弱性的测试( Brittle tests)-- 那些过度指定预期结果或依赖广泛和复杂的模板 -- 实际上是 “抵制改变”。
如果你曾经对一个功能做了 5 行的修改,却发现有几十个不相关的、被破坏的测试,你就会感到 Brittle tests 的问题。随着时间的推移,这种问题会使一个团队不愿意进行必要的重构以保持代码库的健康。
Brittle tests 的一些最严重的情况是来自于对模拟对象的误用。谷歌的代码库因滥用模拟框架而受到严重影响,以至于一些工程师宣布 "不再使用模拟"。
除了 Brittle tests 引起的问题外,更大的测试套件的运行速度也会更慢。测试套件越慢,它的运行频率就越低,它的作用就越小。我们使用一些技术来加速我们的测试套件,包括并行化执行和使用更快的硬件。然而,这类技巧最终会被大量单独的慢速测试案例所淹没。
测试会因为很多原因变得缓慢,比如启动系统的重要部分,在执行前启动仿真器,处理大型数据集,或者等待不同的系统同步。测试开始时往往足够快,但随着系统的增长,速度会变慢。例如,也许你有一个集成测试,行使单一的依赖关系,需要 5 秒钟的反应,但随着时间的推移,增加到依赖十几个服务,同样的测试需要 5 分钟......
与大型测试套件相处的秘诀是尊重它。激励工程师关心他们的测试,就像奖励他们推出一个伟大的功能一样。设定适当的性能目标,重构缓慢或边缘的测试。基本上,就是把测试当作生产代码。
除了发展适当的激励文化外,还可以投资测试基础设施,减少需要支持的框架和工具的数量,以提高时间效率。
三个关键举措帮助自动化测试进入公司的 “重要关注”。定向班、测试认证计划和在厕所里测试。每一项都以完全不同的方式产生了影响,它们共同重塑了谷歌的工程文化。
- Orientation Classes
谷歌早期的入职培训项目大多涉及医疗福利和谷歌搜索如何工作等问题,但从 2005 年开始,也开始包括一个小时的自动化测试价值的讨论。这个培训还包括如何编写一个好的测试。对于当时的许多新人来说,这样的课程是他们第一次接触到这些材料。最重要的是,所有这些想法都是作为公司的标准做法来介绍的。
当新人在入职培训后加入团队后,他们就习惯于写测试,并质疑团队中那些没有写的人。现在,自动化测试已经在行业中得到了更广泛的应用,所以大多数新员工在来到这里时,对自动化测试的期望已经很坚定了。
- 测试认证
最初,我们的代码库中较大和较复杂的部分似乎对良好的测试实践有抵触。有些项目的代码质量很差,几乎无法测试。为了给项目提供一个明确的前进道路,测试小组设计了一个认证计划,他们称之为 “测试认证”。测试认证的目的是让团队了解他们的测试过程的成熟度,更关键的是,提供如何改进测试的 “介绍说明”。
该计划分为五个级别,测试认证第一级涵盖了基础知识:建立持续构建;开始跟踪代码覆盖率;将所有测试分类为小型、中型或大型;识别(但不一定要修复)不稳定的测试;创建一套可以快速运行的快速(不一定全面)测试。
随后的每一级都增加了更多的挑战,如 "不发布有问题的测试 "或 "删除所有非确定性的测试"。到了第五级,所有的测试都是自动化的,快速测试在每次提交前都在运行,所有的非确定性都被移除,每一个行为都被覆盖。
到 2015 年,测试认证项目被自动化方法取代时,它已经帮助 1500 多个项目改善了他们的测试文化。
- “厕所里的测试”
在测试小组用来改善谷歌测试的所有方法中,也许没有一种方法比 "厕所里的测试"(TotT)更离谱。TotT 的目标相当简单:积极提高整个公司的测试意识。问题是,在一个员工分散在世界各地的公司里,怎样做才是最好的?
经过一番头脑风暴之后,有人提出了在洗手间的隔间里张贴传单的想法,作为一个玩笑。我们很快就认识到了其中的天才之处:无论如何,卫生间是每个人每天至少要去一次的地方。不管是不是玩笑,这个想法实施起来很便宜,必须要试一试。
2006 年 4 月,一篇涵盖如何改进 Python 测试的短文出现在整个 Google 的洗手间。反应两极化:一些人认为这是对个人空间的侵犯,他们强烈反对,邮件列表中的抱怨声此起彼伏;但 TotT 的创造者们却很满意:抱怨的人仍然在谈论测试。
最终,骚动平息了,TotT 迅速成为谷歌文化的主打产品。到目前为止,来自整个公司的工程师已经制作了几百集,几乎涵盖了可以想象的测试的每一个方面(除了各种其他技术主题)。新的剧集被热切期待,一些工程师甚至自愿在他们自己的大楼周围张贴剧集。我们有意将每一集的篇幅限制在一页,以挑战作者专注于最重要和可操作的建议。
具有讽刺意味的是,对于一个出现在比较隐蔽的地方的出版物来说,TotT 已经产生了巨大的公共影响。大多数外部访客在访问中都会看到一集,而这样的接触往往会导致有趣的对话,即 “谷歌人” 似乎总是在思考代码问题。
尽管开始时只是一个玩笑,但 TotT 在测试小组发起的所有测试活动中,运行时间最长,影响最深远。
与 2005 年相比,今天谷歌的测试文化已经有了长足的进步。 新人们仍然参加关于测试的指导课程,TotT 几乎每周都会被分发。然而,测试的期望已经更深入地嵌入到日常的开发人员工作流程中。
谷歌的每一个代码变更都需要经过代码审查。而每一个变化都要包括功能代码和测试。评审员被要求对两者的质量和正确性进行评审。事实上,如果一个变化缺少测试,阻止它是完全合理的。
作为测试认证的替代,我们的一个工程生产力团队最近推出了一个叫做项目健康(pH)的工具。pH 工具不断收集关于项目健康的几十个指标,包括测试覆盖率和测试延迟,并在内部提供这些指标。一个 pH 值为 1 的项目被认为是团队需要解决的问题。几乎每个运行持续构建的团队都会自动得到一个 pH 值。
随着时间的推移,测试已经成为谷歌工程文化的一个组成部分。我们有无数的方法来加强它对整个公司工程师的价值。通过培训、温和的劝说、指导,甚至是一点友好的竞争,我们已经创造了明确的期望,即测试是每个人的工作。
为什么我们不一开始就强制要求编写测试?
测试小组曾考虑向高层领导要求测试任务,但很快决定不这样做。任何关于如何开发代码的授权都会严重违背谷歌的文化,并且可能会减缓进展,这与被授权的想法无关。我们的信念是,成功的想法会传播,所以重点变成了展示成功。
如果工程师们决定自己写测试,这意味着他们已经完全接受了这个想法,并可能继续做正确的事情,即使没有人强迫他们这样做。
自动化测试并不适合所有的测试任务。例如,测试搜索结果的质量往往涉及到人的判断。我们使用搜索质量评测员进行有针对性的内部研究,他们执行真实的查询并记录他们的印象。同样,在自动测试中很难捕捉到音频和视频质量的细微差别,所以我们经常使用人工判断来评估电话或视频通话系统的性能。
除了定性判断外,还有某些人类擅长的创造性评估。例如,搜索复杂的安全漏洞是人类比自动化系统做得更好的事情。在人工发现并理解了一个漏洞之后,它可以被添加到一个自动化的安全测试系统中,比如谷歌的云安全扫描器(Cloud Security Scanner),在那里它可以被连续和大规模地运行。
这种技术的一个更概括的术语是探索性测试(Exploratory Testing)。探索性测试从根本上说是一种创造性的工作,有人将被测试的应用程序视为一个有待破解的难题,可能是通过执行一组意想不到的步骤或插入意想不到的数据。在进行探索性测试时,要发现的具体问题在开始时是未知的。它们是通过探测通常被忽视的代码路径或来自应用程序的不寻常的反应而逐渐发现的。与安全漏洞的检测一样,一旦探索性测试发现了问题,就应该增加一个自动化测试,以防止未来的回归。
采用开发者驱动的自动化测试是谷歌公司最具变革性的软件工程实践之一。它使我们能够以更大的团队建立更大的系统,比我们想象的要快。它帮助我们跟上了技术变革的步伐。在过去,我们已经成功地改造了我们的工程文化,将测试提升为一种文化规范,如今,我们对质量和测试的承诺比以往任何时候都要强烈。