测试覆盖率在Google的状况(2019年8月)

| 2022-09-10

谷歌得出的结论:

根据十多年关于测试覆盖率上的投入以及经验教训,谷歌建议如下:

  • 应该在开发工作流程的关键点自动衡量覆盖率。
  • 应该在开发人员常用的工具中显示覆盖率信息。
  • 应该投入精力来处理由覆盖率工具引起的计算错误。
  • 应该依靠现有的、完善的计算覆盖率的库;这些功能足够日常使用。
  • 应该使用单一且统一的集成接口下为所有编程语言进行覆盖计算。

TL;DR;(Too Long,Don’t Read)

1. 前言

代码覆盖率是衡量测试套件覆盖被测软件系统的程度。虽然覆盖率真在软件工程研究中已经很好地建立起来,但是,在工业中的部署常常因被感知到的有用性不足和大规模分析覆盖的计算成本所抑制。在谷歌,覆盖率信息是按每天10亿行代码计算的,包括 7 种编程语言。让覆盖率信息具有可操作性的关键点在于:将其应用于代码变更集的覆盖率指标器和代码评审之中。

本文描述了谷歌的代码覆盖基础设施,以及如何可视化和使用代码覆盖率信息。它还描述了大规模地使用代码覆盖所面临的挑战及其解决方案。为了研究代码覆盖率是如何被开发人员使用与感知的,本文分析了5年内的使用率、错误率和平均代码覆盖率,并报告了从 3000 名开发人员那里收到的 512 份调查问卷。最后,本文对如何在工业环境下实现和使用代码覆盖率提出了具体的建议。

代码覆盖率是软件工程中一个很好的概念,代码覆盖标准,如语句覆盖、分支覆盖和修改的条件/决策覆盖(MC/DC)是测试套件充分性的典型度量。例如,对于安全性要求很高的系统(RTCA DO-178)需要MC/DC充分性。代码覆盖率真已经被知道并应用了几十年。

例如,Piwowarski 等人提到 IBM 在 20 世纪 60 年代末就进行过代码覆盖率测量,而 Elmendorf 早在 1969 年就为对操作系统测试时使用分支覆盖率提供了一个相当健壮的策略。

虽然代码覆盖率通常用于软件工程研究,但它在实践中改进测试的有效性仍然是一个有争议的话题,而且它在行业中的应用也不是广泛的。IBM 对代码覆盖率使用情况的一项大型调查显示,一旦采用了代码覆盖率,测试套件的质量就会提高;然而,易用性和可伸缩性是决定是否首先采用代码覆盖率的主要因素。现有关于代码覆盖率的行业报告通常基于一个或两个编程语言的可管理大小的代码库。然而,即使是这样的规模,这些报告也引起了人们对代码覆盖的成本效益的担忧。他们提出了一个关键问题:与测试套件质量的提高相比,计算代码覆盖率所需的机器资源以及处理结果所需的开发人员时间都太昂贵了。

为了解决和克服这些问题,谷歌花了十多年时间完善其覆盖率基础设施,并实施和验证了学术方法。谷歌的基础架构支持 7 种编程语言,并可扩展到 10 亿行代码的代码库,每天接收数万次提交。本文详细介绍了谷歌的代码覆盖基础架构,并讨论了许多技术挑战和设计决策。这个覆盖率基础设施集成在开发工作流的多个点上。本文通过分析 5 年的历史数据和从 3000 名开发者那里收到的 512 份回复,描述了这一集成,并报告了谷歌采用代码覆盖率真的情况和其所感知到的有用性。

总之,本文做出了以下贡献:

  • 可以基于现有的、已建立的库实现代码覆盖基础设施,无缝地将其集成到开发工作流中,并将其扩展到行业规模的代码库。

  • 它详细介绍了谷歌的持续代码覆盖计算基础设施,以及如何可视化代码覆盖。

  • 它详细说明了五年内的采用率、错误率和平均代码覆盖率。

  • 它报告了代码覆盖率的有用性,分析了来自谷歌开发人员的 512 份调查问卷。

本文的其余部分结构如下。第2节提供了背景资料和术语。第三节详细介绍了谷歌的代码覆盖基础设施。第4节描述了代码覆盖率的采用及其有用性。第5节讨论相关工作。最后,第6节总结并讨论了未来的工作。

2 基本概念与术语

代码覆盖率是一个公认的概念,但也是一个广泛的术语,在实际应用中需要更精确的定义。本节定义了贯穿本文的重要术语和概念。

2.1 项目与变更列表

理解 Google 的开发流程及其覆盖范围的集成需要以下两个概念:

Project:是指某一项目负责者在项目定义的文件中所声明的代码集合(A project is a collection of code that the project owners declare in a project definition file. )。每个项目的定义文件通常包含一组代码路径(即描述一组文件的模式)和一组测试套件以及组织元数据(如联系人电子邮件地址)。

ChangeList:是向谷歌代码仓库提交的一次原子变更。它由一个文件列表、要对这些文件执行的操作,以及可能要修改或添加的文件内容组成。

图1用一个例子说明了变更列表CL的生命周期。

  1. CL1 表示代码基线的开始状态。
  2. 假设此时开发人员发出一个「change」命令,该命令创建一个新的变更列表 CL2。
  • 最初,这个新创建的变更列表 CL2 处于「挂起」状态,此时,开发人员可以编辑其内容。它通过所谓的“快照方式”(在图中表示为S1到S5)一次一次向前演进。
  1. 当开发人员对变更列表的内容感到满意,他们就会发出一个「mail」命令启动代码评审过程。
  • 此时,自动分析(包括代码覆盖率)将启动,并通过电子邮件向评审员发送通知。
  • 变更单现在的状态被称为“评审中”。
  1. 在评审过程中,CL 的内容可能会发生变化,从而导致不同的快照(如图中 S2 和 S3 )。
  2. 当评审者与作者都同意了该 CL 时,作者将发出「submit」命令。
  • 在上面的示例中,此命令首先将 CL2(S3)与代码库的「CL4」合并为CL2(S4)。如果合并成功,则运行更多的自动测试。
  1. 只有在合并成功且所有测试都通过时,才会向代码库提交变更列表。
    • 为了确保代码库中的变更列表号单调递增,提交时 CL2(S4) 也被重命名为 CL5。(CL2 变成一个指针,任何访问它的人都将被转向到 CL5。)
  2. 如果「submit」命令失败,变更列表将返回到“评审中”状态,CL 作者可以继续更改其内容,通常是解决合并失败或测试失败的问题。

2.2 代码覆盖率

谷歌使用代码覆盖基础设施来测量代码行覆盖率,它是一组测试执行的源代码行的百分比。请注意,空行和注释不算作代码行,因此对分母没有贡献

由于谷歌对所有主要语言都强制要求代码样式指南,所以,行覆盖率与谷歌代码库中的语句覆盖率密切相关。图 2 给出了每行语句数的分布。大多数行只包含一个语句,而具有多个语句的行主要是一些循环(初始化、条件、更新)和非平凡的嵌套返回语句(non-trivial, nested return)。

对于C++、Java 和 Python ,谷歌的代码覆盖率工具也可以计算分支覆盖率,即由一组测试执行的条件语句(例如if语句)分支的百分比。分支覆盖率相当于控制流图中所有条件边的边覆盖率

Google 区分了两种覆盖范围:

  • 项目覆盖率:项目测试套件在项目代码路径上的行覆盖率。对于给定的项目,每天计算一次项目覆盖率。

  • 变更列表CL覆盖率:受变更列表影响的所有项目的所有测试套件的行覆盖率的联合,即代码路径出现在变更列表差异中的所有项目。对于给定的变更列表,变更列表覆盖率计算一次或多次。在变更列表覆盖范围内,区分了:

    • CL 总覆盖率:变更列表中文件的所有内容的代码覆盖率。
    • CL 差异覆盖率(Delta 覆盖率):仅对变更列表中修改或添加的行的代码覆盖率。因为已删除的行不再存在了,也就无法覆盖。

2.3 单元测试

这里只考虑在一台机器上执行的测试的代码覆盖率,通常是在一个进程中。在谷歌,这些测试用例俗称为单元测试。 不考虑跨多台机器的更复杂测试用例的代码覆盖率,如集成测试或系统测试。因为集成测试关注的是(子)系统之间的集成点,而不是每个(子)系统的内部,因此对于这些测试来说,「行覆盖」这个指标并不是最适合这类测试目的的覆盖度量。

3. 基础设施

自 2012 年以来,就一直在积极开发的谷歌代码覆盖基础设施。此后,它不断发展,以提高兼容性并应对许多挑战。图 3 给出了一个高层体系结构概述,显示了基础结构的四个主要层,这些层将在后面的章节中描述。

3.1 覆盖率基础设施

谷歌代码覆盖基础设施利用了成熟的代码覆盖库和内部开发的解决方案的组合,来支持各种程序设计语言的覆盖率计算。具体而言,基础设施使用:

  • C++: gcov [7].
  • Java: JaCoCo [5] and additional, internal support infrastructure for Android.
  • Python: Coverage.py [2].
  • Go: Go has built in coverage support (go -cover) [6].
  • JavaScript: Istanbul [4] and an internally developed coverage library, plus significant support infrastructure built around them.
  • Dart: Dart-lang has built in coverage support [3].
  • TypeScript: Re-uses much of the JavaScript infrastructure.

为了解决「集成所有这些库并统一其输出结构」挑战,我们在这些外部库之上进行修改或构建。有的覆盖率工具被修改成可以生产 lcov 格式的输出,有的则被包上一层谷歌自己开发的工具基础设施,以便将其输出转换为 lcov 格式。

lcov 格式是 gcov 及其前端工具 lcov 用来存储覆盖率信息的基于文本的跟踪文件格式。谷歌代码覆盖基础设施对这个格式稍加修改,所以,其分支覆盖符号略有不同,不需要内部 GCC id 来标识代码分支。

在概念方面,lcov 支持行覆盖和分支覆盖的格式,但也可以模拟其他类型的代码覆盖;例如,只需将每个函数的第一行标记为 instrumented ,就可以模拟函数覆盖。专注于行覆盖被证明是一个很好的实际选择,因为它与语句覆盖有很强的相关性(回想图2),并且易于可视化和理解。

3.2 覆盖率工具与构建系统的集成

谷歌使用 Blaze 作为其核心构建系统。Blaze 是 Bazel 构建系统的内部版本,支持构建二进制文件、运行测试用例和收集代码覆盖率信息。

触发覆盖率计算的一种方式是使用 Blaze 命令行接口,对一组指定的代码路径调用 coverage 命令。Blaze 自动处理覆盖率计算,抽象底层结构库的实现细节。表 1 的数据表明,很少有人通过手动方式调用覆盖率计算。例如,每天只有大约 0.5% 的开发人员会手动调用 blaze 的覆盖率命令。

3.3 覆盖率的自动化

图 4 说明: 行号(用红色矩形突出显示)被着色以显示覆盖率信息。行号被涂成:

(1)绿色:表示该行被覆盖 (2)橙色:表示该行未被覆盖 (3)白色:表示该行未被检测

注意,线条本身也是彩色的。然而,这与覆盖率无关,而是与代码更改有关。如果一行被添加到此变更列表中,则该行用绿色着色;如果该行被删除,则该行用红色着色。

使用颜色可视化覆盖率和代码更改信息是很棘手的。我们发现,在覆盖和代码更改方面使用绿色和红色会让开发人员感到困惑,在覆盖方面使用绿色和橙色会更好。

谷歌代码覆盖基础设施在 Blaze 之上提供了进一步的自动化,将覆盖计算集成到常规的开发工作流中。通过在项目定义中设置一个布尔选项( enable_coverage=true ),开发人员可以轻松地为项目配置自动化的覆盖率计算。

一旦为一个项目启用了覆盖率开关,这个自动化系统每天会为其计算一次项目覆盖率。自动化系统还会计算该项目中所有变更列表 (CL)的代码覆盖率。

(1) 对于每个 CL,在首次发起代码评审时,一定会执行一次覆盖率计算。 (2) 如果该 CL 内容在评审过程中做过修改,则在该 CL 最终提交到代码库之后,一定会再执行一次覆盖率计算。 (3) 任何开发人员都可以在任何时候通过在代码评审工具的界面上单击一个按钮,来请求对该 CL 执行一次覆盖率计算。事实上,CL 的作者或评审者在评审过程中若发现其内容发生了重大修订,则也会请求新的覆盖率计算。 (4) 如果一个 CL 修改多个项目中的代码,则计算结果将合并在一起。

谷歌覆盖率自动化基础设施利用覆盖率工具和单个测试运行的 lcov 格式输出,生成合并后的覆盖率报告,不会存储单独的 lcov 输出,只为每个文件存储两个(运行长度编码的)序列,一个序列编码对应表示「是否为检测行」,另一个序列编码表示「是否覆盖了检测行」,而原始的 lcov 输出会被丢弃。这种压缩是一种重要的资源优化,解决了一个非常关键的可伸缩性挑战。任何其他更详细的格式在规模上存储都会非常昂贵。在使用两个序列编码的七年中,它从未限制基础设施的可用性;这也说明,可能真的不需要更具表现力的格式了。

另一个技术挑战在于确保成功的覆盖率计算。为了提高成功率,项目覆盖率只根据提交的变更列表计算,Google测试自动化平台(TAP [12])已经确定所有测试都通过了这些变更列表。对于变更列表覆盖率,基础设施等待预先配置的时间量(当前为10分钟)以确定是否有任何测试失败,在这种情况下,将不会计算覆盖率。虽然这避免了在测试失败的情况下计算代码覆盖率的徒劳尝试,但并不能保证覆盖率计算成功。在启用覆盖率检测的情况下执行测试需要更多资源并更改执行环境。要解决这些差异以进一步提高成功率,需要针对项目代码或配置中的各个边角场景coner case进行数百个特定的修复。

3.4 可视化与数据分析

Google的代码覆盖基础设施以几种不同的方式可视化覆盖信息:CL coverage在Critique中可视化(它是Google的代码评审系统)。项目覆盖率在CodeSearch( https://cs.chromium.org/) 和开发人员的IDE中可视化。最后,基础设施为项目覆盖率提供了趋势可视化,覆盖率数据可以查询以进行数据分析。

可视化:在Critique中,变更列表覆盖率显示为每个受变更影响的项目,并在摘要中报告。如图4所示,每一行的行覆盖率都是可视化的,每个文件和整个变更列表的聚合覆盖率都是用数字报告的,如图5所示。分支覆盖是为底层库支持它的语言提供的,在代码中以可视化的形式并作为聚合。

CodeSearch[1]的一个内部版本帮助Google开发人员导航这个包含超过10亿行代码的存储库。代码搜索使浏览文件、交叉引用符号和查看版本历史记录变得容易。它还便于绘制行覆盖,如图6所示。基础数据来自每日的项目覆盖率计算,并且仅对自上次计算以来未更改的文件可视化。 代码编辑器和IDE是可视化代码覆盖率的另一个自然选择。使用与代码搜索相同的数据源,我们为许多编辑器提供自定义支持。例如,我们的Vim元插件是开源的[8]。 因为几乎所有处理代码的工具都有行号,所以行覆盖率是可视化的一个不错的选择。尽管覆盖集成在概念上对所有程序明语言都是相同的,但程序明语言之间存在细微的差异。例如,块的左大括号是否计入行覆盖率在不同语言之间是不同的,有时甚至在基于大括号之前的代码的单一语言中也是如此。然而,根据我们的经验,这些概念上的差异似乎并不重要:开发人员在上下文中解释可视化,而只是忽略这些小的特性。

3.5 数据分析

除了可视化变更列表和项目覆盖率之外,覆盖率基础设施还支持趋势可视化和数据分析。例如,开发人员可以使用预先构建的趋势可视化用户界面,在更长的时间内跟踪项目覆盖率。同样,开发人员可以使用Dremel[22]查询系统发出特定的查询。所有覆盖率数据都存储在中央数据库中,用于交互式分析。这主要是由开发人员或研究人员使用的,他们正在分析大量的覆盖率数据,类似于本文中报告的数字。

4 覆盖率的采纳和有用性

本节报告了分析多年历史数据和调查谷歌3000名开发者的结果。具体来说,本节展示了: (1) 谷歌的开发人员如何采用并将代码覆盖率集成到他们的工作流程中; (2) 代码覆盖率在谷歌是如何随着时间的推移而发展的; (3) 代码覆盖率的感知有用性是什么。

代码覆盖在谷歌已经使用了至少13年。据我们所知,最早的自动代码覆盖基础设施的内部记录计划可以追溯到2006年。

第3节所述的基础设施是报告结果的基础,是最新版本,自2012年以来一直在使用。

在2012和2018之间,它收集了大约13000000个日常项目覆盖测量和14000000个变更列表覆盖测量。

4.1 代码覆盖率的采纳

代码覆盖率计算在整个Google中不是强制性的。但是,项目(或项目组)可以选择在其团队中强制使用代码覆盖率。此外,单个团队可以选择自动代码覆盖率计算,单个开发人员可以用交互方式发起调用,进行代码覆盖率计算。

回想一下表1,它显示了开发人员手动交互调用blaze覆盖率的百分比。这方面的一个典型用例是在创建变更列表之前计算代码覆盖率。只有0.5%的开发人员每天手动调用代码覆盖率计算。相比之下,33.5%的开发人员在任何一天至少在命令行上调用一次blaze测试(即,在不计算覆盖率的情况下运行测试)。这表明开发人员在工作流程中对覆盖率和二进制通过/失败信息的处理方式不同,特别是他们检查覆盖率的频率要低得多。 对于每个变更列表,都会自动计算代码覆盖率。Google的代码审查工具将结果呈现给变更列表的作者和审查者。审阅者可以使用代码覆盖率要求作者改进更改的测试,或者作者可以主动这样做。

图7显示了百分比 谷歌的测试自动化平台,并积极测量项目和变更列表的覆盖范围。该图显示自引入代码覆盖率自动化以来的所有数据。2015年第一季度的增长是谷歌著名的内部“马桶测试”广告的结果[16]。我们推测,如果当时基础设施更好的话,第一季度之后采用曲线的斜率将继续陡峭。 到目前为止,2015年2月是一个逐步完善和改善覆盖基础设施的时期,以允许更多的项目使用它。许多不使用代码覆盖率的项目表示感兴趣,但基础设施不支持它们的代码库(例如,导致测试失败的覆盖率检测)。根据我们的经验(作为覆盖基础设施的所有者和维护者),不受支持的项目通常只有少数由覆盖检测引起的测试失败,但是每个项目都有不同的根本原因。第4.3节提供了更多细节。 出于技术原因,我们不希望变更列表覆盖率使用率达到100%:作为资源优化,覆盖率基础结构仅在该变更列表的所有测试通过时计算变更列表覆盖率。这意味着某些特别被动的项目在给定的时间段内可能根本无法获得成功的变更列表覆盖率计算。因此,虽然理论上最大值为100%,但实际最大值可能更低。相比之下,项目覆盖率的使用率更有可能最终达到100%。请注意,目前还不清楚2018年第二季度变更列表覆盖率的下降情况。

4.2 代码覆盖率

图8说明:每周项目覆盖率是给定周内每日项目覆盖率的中位数。如果某个项目在给定的一周内没有成功的覆盖率计算,则该项目将从该周的聚合中排除。注意,我们排除了2015年之前的数据,因为2015年之前使用覆盖率的项目不到20%(见图7)。

图8显示了2015年至2018年期间所有项目的项目覆盖率中位数以及季度间隔(IQR)。图表显示了每周项目总覆盖率(即给定周的每日项目覆盖率中值)。每周项目覆盖率报告消除了由间歇性测试或基础设施故障引起的异常值。 Google没有在整个代码库中强制任何代码覆盖率阈值。项目(或项目组)可以自由定义自己的阈值和目标。许多项目选择使用一个集中的自愿警报系统,该系统定义了五个级别的代码覆盖率阈值。表2显示了每个级别的标准。我们怀疑,自2016年年中以来,项目覆盖范围的逐步增加反映了我们在2016年公布的这些水平的逐步采用。 当然,安全关键系统有更严格的要求。为了确保高质量,这些项目使用突变测试[23,24],这提供了更严格的充分性标准[19]。

图9 说明:成功率表示每周成功完成的所有自动覆盖率计算的百分比。注意,我们排除了2015年之前的数据,因为2015年之前使用覆盖率的项目不到20%(见图7)。

4.3 覆盖率计算失败的原因

覆盖基础设施每天计算一次项目覆盖率,并为每个变更列表计算变更列表覆盖率(第3.3节提供了更多详细信息)。图9显示了项目和变更列表覆盖计算的覆盖基础设施的成功率。本节描述了覆盖率计算失败的最常见原因。

失败的覆盖率计算的一个常见原因是测试失败,该失败仅在启用测试代码的覆盖率检测时显示。覆盖检测相对昂贵,性能故障是导致覆盖计算失败的最常见原因。在大多数编程语言中,覆盖率检测至少可以防止某些编译器优化。这会导致更长的测试运行时间,从而导致更多的超时。覆盖率检测还会导致较大的二进制文件,从而导致更多的内存不足错误。第二个最常见的原因是测试片状。覆盖仪器可能会恶化非确定性测试的片状性。一个简单的例子是,任何使用sleep命令来允许被测代码完成其工作的测试,在被检测时可能会突然变得不稳定,因为代码比较慢。项目覆盖成功率的逐步提高是一系列基础设施改进的结果,以应对失败的覆盖计算。 注意,变更列表覆盖率计算最初是沿着side TAP实现的,这解释了较低的成功率。当前的覆盖基础设施等待TAP的结果,以避免在测试失败的情况下尝试覆盖计算。图9中变更列表覆盖成功率的显著下降是覆盖基础设施故障,通常在一周内解决。这些故障表明需要维护覆盖基础设施,并且应该为此类维护配置资源。

4.4 代码覆盖的感知有用性

由于覆盖计算和可视化是完全自动化的,并集成到开发人员工作流中,我们希望了解开发人员在多大程度上使用所提供的数据以及代码覆盖的感知有用性。为此,我们在2019年2月进行了一项调查,包括三个利克特量表问题和一个开放式问题。图10显示了调查的屏幕截图。

调查提出了以下问题:“当你是……”时,你多久会发现覆盖率有用?有三种情况(创作,审查、浏览CL)。每个场景都使用相同的5点Likert量表。我们随机选择了3000名谷歌开发人员进行了调查,其中512人(17%)做出了回应。我们从所有承诺至少一行代码的人中随机选择参与者。虽然这个集合主要包含开发人员,但它也包含非工程角色的人员。

图11显示了变更列表封面在三种不同场景中的自报有用性。在编写变更列表时,45%的受访者报告说他们“经常”或“非常经常”使用覆盖范围,25%的人“有时”使用覆盖范围。在审查变更单时,40%的受访者“经常”或“非常经常”使用覆盖范围,28%的人“有时”使用覆盖范围。10%的受访者从不使用保险。总的来说,相当多的开发人员确实定期使用代码覆盖率并从中发现价值。

来自开放式文本框的轶事证据提供了额外的见解。少数受访者不是开发人员,但仍对代码库做出更改,例如技术作者。这些受访者选择“从不”或“不确定”。在量表的另一面,一些受访者选择用“我在创作时使用覆盖率数字以确保我记住了测试”等语句来解释“非常经常”的回答。我发现,在审查时更为重要的是,很难看出边缘案件是否在没有覆盖的情况下迅速得到覆盖”。 我们分析了所有开放式文本框答案的情绪,并将其分为三类:

  • 正向反馈:对代码覆盖概念总体上是正面的回答,即使它们对基础设施提出了一些问题。
  • 负向反馈:对代码覆盖率概念的回答通常是否定的。
  • 中立反馈:没有表达对报道的一般态度的回答。这些报告大多是关于基础设施的问题或对调查本身的评论,例如。“我怀疑我年纪太大了,不适合你的调查。我已经超过12个月没有写过或认真审查过代码了。”

表3显示了这三类答案的分布情况。60%的回答通常对覆盖率是正面的,16%是负面的,24%是中性的。一个有趣的观察是,对代码覆盖率表示负面看法的受访者比例大于不使用代码覆盖率的项目比例。图12显示,一些对覆盖率普遍表示负面情绪的受访者仍然自我报告说,他们“有时”或“很少”使用覆盖率。

5 相关工作

代码覆盖率至少在20世纪60年代就已为人所知,Elmendorf[13]在1969年给出了一个在测试操作系统时使用分支覆盖率的合理的健壮策略。Piwowarski、Ohba和Caruso[25]提到,覆盖率测量是在1960年末在IBM内部进行的。

Yang等人对17种不同的覆盖工具进行了调查。该调查提供了该地区的良好概况,并根据特征对比了工具。但是,它没有分析如何在一致的工作流中最好地集成不同的工具。

Piwowarski等人描述了一个关于IBM测试组织及其在工业环境中使用覆盖率的大型调查。他们发现易用性是采用覆盖度量的主要因素,一旦采用覆盖度量,就会导致测试套件质量的提高。Woo Kim[20]描述了在工业环境中使用 coverage 测试 19.8Kloc 产品。他们得出的结论是,代码覆盖率是有用的,但是详细的代码覆盖率分析并不具有成本效益,因为大多数缺陷都局限在相对较小比例的易出错模块中,而且复杂覆盖率标准和简单覆盖率标准之间有很强的相关性。虽然这些行业报告非常有价值,但它们通常基于有限的代码基大小报告结果,最重要的是,它们往往只分析一种或两种编程语言。

IBM提出了子串洞分析来分析代码覆盖率数据,这使得它在大型系统测试中具有成本效益。这种方法能够直观地推理大量的覆盖信息。为了避免捕获系统测试覆盖率时固有的问题,Chen等人。提出了一种使用执行日志以高精度估计代码覆盖率的自动化方法。Li等人研究了现有的Java分支覆盖工具,发现没有正确测量所有分支结构,而使用我的大多数工具的字节码检测不是一种有效的方法来测量源代码上的分支覆盖率。

结论与更进一步的工作

本文描述了Google的代码覆盖基础设施,如何可视化计算出的代码覆盖信息,以及如何将其集成到开发人员工作流中。代码覆盖基础设施旨在克服诸如规模和编程语言多样性等技术挑战,但我们的经验也表明,在实践中,另一个重要的技术挑战是处理失败的覆盖计算。谷歌需要多年的努力才能充分解决这些问题。使用数据和对开发人员的调查表明,开发人员通常对代码覆盖率的想法持积极态度。他们将其视为日常工作流程中的一个有价值的补充,并使用它(如果可用)。从开发人员的角度来看,实现这种积极情绪的关键是可用性和低开销。

我们希望本文描述的想法和经验能够激励和帮助其他人开发类似的基础设施。根据本文报告的经验教训,我们建议如下:

  • 在开发工作流程的关键点自动测量覆盖率。
  • 在开发人员常用的工具中显示覆盖率信息。
  • 期望投入精力处理由覆盖仪器引起的错误。
  • 依靠现有的、完善的计算覆盖率的库;这些功能足够日常使用。
  • 为在单一统一接口下使用的所有编程语言集成覆盖计算。

我们计划进一步调查使用数据和开发人员的意见,以便更好地了解如何使用覆盖率,以及如何更好地利用覆盖率造福于开发人员。特别是,我们将扩大调查范围,要求更多与覆盖面相关的自报数据,并将这些数据与客观行为数据相关联。最终,确定代码覆盖率的采用是否会带来更好的软件质量是很好的,但是没有一个单一的、可靠的代码质量代理度量。例如,变更列表的数量可能会受到代码质量的影响,但它也可能是一般开发或程序习惯的结果。此外,代码质量并不是使用代码覆盖率的唯一好处。因此,我们希望在今后的工作中解决的一些开放性问题是:

  • 感知有用性与实际有用性有区别吗?例如,在代码评审期间显示覆盖率是否实际上加快了评审过程?
  • 感知和实际的有用性是否取决于变更单的大小?例如,我们推测,当编写完整的特性时,比如一个新类和一个测试套件,开发人员更关心代码覆盖率,而不是编写一个非常小的更改。
  • 在代码复查期间显示覆盖率是否会影响最终的代码覆盖率,也就是说,如果显示覆盖率,测试添加的频率是否会高于不显示覆盖率时的频率?
参考资料

[1] Codesearch. https://cs.chromium.org/. [2] Coverage.py. https://coverage.readthedocs.io/en/v4.5.x/. [3] Dart - Coverage. https://github.com/dart-lang/coverage. [4] Istanbul Code Coverage. https://github.com/istanbuljs. [5] JaCoCo Java Code Coverage Library. https://www.eclemma.org/jacoco/. [6] The Go Blog - The cover story. https://blog.golang.org/cover. [7] UsingtheGNUCompilerCollection(GCC):Gcov.https://gcc.gnu.org/onlinedocs/ gcc/Gcov.html. [8] vim-coverage. https://github.com/google/vim-coverage. [9] Adler,Y.,Behar,N.,Raz,O.,Shehory,O.,Steindler,N.,Ur,S.,andZlotnick, A. Code coverage analysis in practice for large systems. In 2011 33rd International Conference on Software Engineering (ICSE) (2011), IEEE, pp. 736–745. [10] Chen, B., Song, J., Xu, P., Hu, X., and Jiang, Z. M. J. An automated approach to estimating code coverage measures via execution logs. In Proceedings of the 33rd ACM/IEEE International Conference on Automated Software Engineering (New York, NY, USA, 2018), ASE 2018, ACM, pp. 305–316. [11] Chilenski, J. J., and Miller, S. P. Applicability of modified condition/decision coverage to software testing. Software Engineering Journal 9, 5 (September 1994), 193–200. [12] Elbaum, S., Rothermel, G., and Penix, J. Techniques for improving regression testing in continuous integration development environments. In Proceedings of the 22Nd ACM SIGSOFT International Symposium on Foundations of Software Engineering (New York, NY, USA, 2014), FSE 2014, ACM, pp. 235–245. [13] Elmendorf, W. R. Controlling the functional testing of an operating system. IEEE Transactions on Systems Science and Cybernetics 5, 4 (1969), 284–290. [14] Gopinath, R., Jensen, C., and Groce, A. Code coverage for suite evaluation by developers. In Proceedings of the 36th International Conference on Software Engineering (2014), ACM, pp. 72–82. [15] Inc., G. Google C++ Style Guide. https://google.github.io/styleguide/cppguide. html. [16] Inc., G. Introducing “Testing on the Toilet”. https://testing.googleblog.com/2007/ 01/introducing- testing- on- toilet.html, Jan. 2007. [17] Inc., G. Bazel build system. https://bazel.io/, 2015. [18] Inozemtseva, L., and Holmes, R. Coverage is not strongly correlated with test suite effectiveness. In Proceedings of the 36th International Conference on Software Engineering (2014), ACM, pp. 435–445. [19] Just, R., Jalali, D., Inozemtseva, L., Ernst, M. D., Holmes, R., and Fraser, G. Are mutants a valid substitute for real faults in software testing? In Proceedings of the Symposium on the Foundations of Software Engineering (FSE) (Nov. 2014), pp. 654–665. [20] Kim, Y. W. Efficient use of code coverage in large-scale software development. In Proceedings of the 2003 conference of the Centre for Advanced Studies on Collabora- tive research (2003), IBM Press, pp. 145–155. [21] Li, N., Meng, X., Offutt, J., and Deng, L. Is bytecode instrumentation as good as source code instrumentation: An empirical study with industrial tools (experience report). In 2013 IEEE 24th International Symposium on Software Reliability Engineering (ISSRE) (2013), IEEE, pp. 380–389. [22] Melnik, S., Gubarev, A., Long, J. J., Romer, G., Shivakumar, S., Tolton, M., and Vassilakis, T. Dremel: Interactive analysis of web-scale datasets. In Proc. of the 36th Int’l Conf on Very Large Data Bases (2010), pp. 330–339. [23] Petrović, G., and Ivanković, M. State of mutation testing at Google. In Proceedings of the International Conference on Software Engineering—Software Engineering in Practice (ICSE SEIP) (May 2018). [24] Petrović, G., Ivanković, M., Kurtz, B., Ammann, P., and Just, R. An industrial application of mutation testing: Lessons, challenges, and research directions. In Proceedings of the International Workshop on Mutation Analysis (Mutation) (Apr. 2018), pp. 47–53. [25] Piwowarski, P., Ohba, M., and Caruso, J. Coverage measurement experience during function test. In Proceedings of the 15th international conference on Software Engineering (1993), IEEE Computer Society Press, pp. 287–301. [26] Yang, Q., Li, J. J., and Weiss, D. M. A survey of coverage-based testing tools. The Computer Journal 52, 5 (2009), 589–597. [27] Zhu, H., Hall, P. A., and May, J. H. Software unit test coverage and adequacy. ACM Computing Surveys (CSUR) 29, 4 (1997), 366–427.