The Google Testing Law (谷歌测试定律)

测试金字塔

Posted by 肖哥shelwin on August 4, 2017

什么是谷歌测试定律?

软件测试(Software Testing)是软件工程(Software Engineering)中不可或缺的一个过程。软件测试触发预定义的测试步骤、比较软件的实际输出结果和预期输出结果,以此来评价软件质量(Quality),判断软件的实现是否满足设计目标和用户需求。只有经过严格测试的软件,才能发布给用户使用。在实际中,根据测试阶段的不同,软件测试可以分为:

  • 单元测试: 测试对象通常是一个函数(Function)或一个类(Class)。单元测试与软件代码高度相关,通常由开发人员自己完成。

  • 组件测试: 测试对象通常是一个模块(Module),目的是验证模块的功能是否满足设计目标。组件测试通常和软件开发同步进行。

  • 集成测试: 测试对象可以是一个独立软件实体(Entity)的对外接口(本质上测试的是这个软件实体对外呈现的功能);也可以是多个相邻软件实体相互之间的接口(本质上测试的是多个相邻软件实体呈现的整体功能)。集成测试聚焦于软件功能,一般在软件开发部分或全部完成后进行。

  • 系统测试: 测试对象是包含了所有软件实体的真实系统。系统测试从用户的角度设计测试步骤,目的是检验系统是否满足用户需求。一般在系统所有软件都开发完成后进行。

在谷歌,测试的分类更多地强调测试范围,而不是测试阶段。具体来说,谷歌把软件测试分为:

  • Small Tests(小范围测试): 通常对应单元测试和组件测试。

  • Medium Tests(中等范围测试): 通常对应集成测试。在谷歌,Medium Tests强调的测试对象是相互之间有直接接口或互操作(Interoperation)关系的相邻软件模块/软件实体。

  • Large Tests(大范围测试): 通常对应系统测试。

在长期的测试实践中,谷歌发现,不同的测试范围或阶段中发现的软件Bug(即缺陷、漏洞,下同),其解决成本(Fixing Cost)具有极其显著的差别。举例来说,解决一个小范围测试(单元测试)中发现的软件Bug需要花费的成本是5美金左右,解决一个中等范围测试(集成测试)中发现的软件Bug的成本在500美金左右,而解决一个大范围测试(系统测试)中发现的软件Bug的花费则高达5000美金。

title

谷歌的这一经验数据在软件行业中引起了广泛共鸣、得到了许多人的认同。在这里,笔者以更加科学的方式来描述谷歌的这一发现,并将其命名为谷歌测试定律

[谷歌测试定律]. 随着测试阶段的推进(Small Tests -> Medium Tests -> Large Tests,或单元测试 -> 组件测试 -> 集成测试 -> 系统测试),测试中所发现的软件Bug的解决成本呈指数级增长。

为什么谷歌测试定律成立?

软件Bug一旦被发现,我们需要做的事情是确定的,那就是找到软件Bug产生的原因、修改软件代码、然后验证代码的修改是否确实解决了Bug。因此:

Bug解决成本 = Bug定位成本 + 代码修改成本 + 修改验证成本

  • Bug定位成本: 一旦发现Bug,首先需要回答的是, 引起Bug的原因是什么?谁负责解决Bug? 不同的测试阶段最大的区别就是被测对象(Tested Object)不同。被测对象既可以小到一个函数,也可以大到整个系统。从技术角度来说,被测对象的范围越广,可能引起Bug的嫌疑模块就越多,精确地找到真正引起Bug的模块就越困难;从组织角度来说,被测对象的范围越广,牵涉到的部门和个人就越多,针对Bug产生原因和Bug责任人的反复讨论和沟通所耗费的成本就越高。根据经验,随着测试阶段的推进,被测对象的范围将成倍扩大,牵涉到的人员也将成倍增加,因此通常来说Bug定位的成本也是成倍增加的

  • 代码修改成本: 一般来说,只要精确地找到了产生Bug的软件代码,那么解决Bug所带来的代码改动量是确定和一致的。因此,不同的测试阶段所发现的软件Bug,其需要的代码修改成本可以认为是相等的。

  • 修改验证成本: 软件Bug的解决意味着软件代码的改动(Change)。一切代码改动都需要被充分地验证。我们需要确保: (1)代码的改动确实解决了我们发现的Bug,(2)代码的改动没有破坏任何已有的功能。在执行层面,最基本的一个要求就是,修改后的软件包需要被拿到发现软件Bug的测试环境中去验证。如果依然存在问题,那么代码修改无效,需要重新修改;只有测试用例通过了,才能认为修改是有效的。一般来说,越往后期的测试阶段,测试的环境越复杂,测试的执行时间越长,测试花费的人力成本越高。举例来说,单元测试可能只要数秒钟就能完成并且一定是自动化的,而系统测试则可能需要消耗人力并要一天甚至几天才能完成。因此,随着测试阶段的推进,软件改动的验证所花费的成本是成倍增长的

综合Bug定位成本、代码修改成本和修改验证成本,我们发现,软件Bug的解决成本确实是随着测试阶段的推进成倍增加的。从数学角度看,总的趋势就是指数级增长的。至于这个指数曲线的底数的大小(决定指数曲线增长幅度),虽无法精确地给定,但可以确定的是,当软件项目越大型、软件架构越复杂、参与人员越分散时,指数曲线的底数就越大,Bug解决成本的增长幅度就越快

谷歌测试定律的启示

  • 测试资源要向前期测试阶段倾斜。为何要把有限的测试资源更多地投入到前期测试阶段?笔者从可行性和必要性两个方面给出回答。在讨论可行性之前,我们明确:

    [测试追溯定律]. 软件测试的各个不同阶段,凡是在当前测试阶段发现的软件Bug,一定可以在前一个测试阶段或更早的测试阶段,通过修改或者增加一个测试用例来重现。

    在实际中,由于各种因素(测试覆盖度不够高、测试力度不够强、测试工具不够可靠、测试样本不够可信等),软件Bug可能会遗漏到后续的测试阶段。但是从理论上说,每个测试阶段都是有可能发现全部潜在软件Bug的。通过加大前期测试的投入、优化前期测试的过程、提升前期测试的效果,来减少遗漏到后期测试阶段的Bug数量,这条路是可行的。另外,根据谷歌测试定律,同一个软件Bug,在后期测试阶段被发现,相比在前期测试阶段被发现,其解决成本可能要高一个数量级。为了节省公司成本、提高产品质量,我们应该尽可能地在前期测试阶段发现更多的Bug。为此,我们务必要确保前期测试的有效性和覆盖度。前期测试阶段的高度自动化,有助于实现这样的目标。在总的测试资源有限的情况下,将更多的测试资源投入到前期测试阶段是必要的。反过来说,如果前期阶段的测试不充分,导致大量本该在前期测试阶段被发现的Bug遗漏到后期测试阶段才被发现。到时候,我们可能需要投入巨量的人力物力去解决这些Bug。这对部门和公司来说,将是难以承受之重。

  • 测试工作要尽可能早地开展。在敏捷时代,测试无须等待软件开发完成之后才展开,而是与软件开发同步进行。具体来说,在每个迭代周期,软件开发致力于交付一个或多个可供用户使用的功能点。这有助于测试工作的提早介入。测试开展得越早,软件Bug发现得也就越早,解决软件Bug的成本也就越低。在实际中,测试工作的开展不仅受制于软件开发进度,还受制于测试自身所依赖的外部软件和工具。通过使用模拟器技术(即Mock),我们可以减少对外部的依赖,不仅避免测试进度受制于人,而且将测试更多地聚焦在被测对象身上。

  • 千方百计缩短测试时间。狭义的测试时间指测试步骤的执行时间,广义的测试时间指从开发人员提交代码到获得测试反馈结果的时间间隔。缩短测试时间,不仅有利于提升软件测试的生产力(单位时间执行更多的测试),而且有利于提升软件开发的生产力。很多时候,软件开发是一个反复提交代码的过程。如果测试的验证速度很快,那么代码的提交就会更频繁,软件开发的效率也就得到了提高。任何一个测试阶段,无论是单元测试还是系统测试,加快测试速度、缩短反馈时间,都是很重要的。在实际中,通过改进系统的可测性、并行或分布式执行测试用例等,可以有效地提高测试速度、缩短测试时间。

  • CBRT: 基于代码改动的回归测试。所谓CBRT(Change-Based Regression Testing),指的是每次代码改动均执行回归测试用例。在软件开发中,代码的改动(Change)是常态。新功能实现、Bug修复、代码重构等都会带来代码的修改。回归测试(Regression Testing)是确保代码改动不破坏已有功能的重要举措。然而,回归测试能不能发挥更大的作用,与回归测试的执行时间有关系。是代码每次改动就执行回归测试,还是许多改动合在一起后再执行回归测试,有很大差别。前者,回归测试一旦发现Bug,责任人是清楚的,解决Bug也更容易;而后者,回归测试一旦发现Bug,单单排查原因、找到责任人就需要耗费大量的时间。因此,代码一旦发生改动就立即执行回归测试是很有必要的。在谷歌,考虑到每次代码改动均执行所有回归测试(测试集可能非常大)带来的开销较大。为此,基于对代码模块和测试用例的关联度分析,在谷歌,每次代码改动只执行回归测试子集,即只执行那些可能受到被改动代码影响的测试用例的集合。

  • 对测试遗漏出去的每一个Bug进行EDA。无论前期测试做得如何好,我们都不能百分之百保证不会遗漏Bug到后期测试阶段。也就是说,只要后期测试阶段发现了软件Bug,那就意味着前提测试阶段具有改进的空间。那么,如何持续地改进前期测试呢? 笔者认为,针对每一个遗漏到后期测试阶段的软件Bug,至少有两件事情是可以做的。首先,开发人员需要做代码改动,而前期测试人员也应该针对测试用例做改进。根据测试追溯定律,后期测试发现的软件Bug一定可以通过修改或增加一个前期测试用例来复现。这样,我们可以基于改进后的前期测试用例对代码改动进行验证。另外,前期测试人员需要进行EDA(Escaped Defect Analysis),即遗漏问题分析。不仅要分析为什么问题被遗漏了,更要给出具体和切实可行的改进措施,以举一反三,避免此类错误再次发生。只有持续地改进,我们才能把前期测试工作做得越来越好,从而最大程度减少遗漏到后期测试阶段的Bug数量。

我是肖哥shelwin,一个高质量软件工程实践者和推动者。欢迎扫描下方二维码,添加我的个人公众号测试不将就,获得更多自动化测试, 持续集成, 软件工程实践, Python编程等领域原创文章。

公众号