测试覆盖率

设计初衷

软件测试是一种帮助团队持续交付优质代码的常见做法。用于测试软件行为的不变性,捕获和防止功能或其他所需属性回归,以及帮助扩缩工程流程。

在源代码行覆盖率方面衡量测试覆盖率有助于工程师发现其测试解决方案中的不足。使用测试覆盖率作为指标,可以推广更高质量的软件和更安全的开发做法。持续衡量测试覆盖率有助于工程师保持较高的质量标准。

测试范围并不保证代码没有错误。测试应与其他工具(例如模糊测试、静态和动态分析等)一起使用。

绝对测试覆盖率

绝对测试覆盖率衡量的是整套测试所涵盖的所有源代码行。Fuchsia 的持续集成 (CI) 基础架构会生成绝对覆盖率报告,并会不断刷新此类报告。“覆盖率”报告通常最多会过时几个小时。

“绝对覆盖率”信息中心

如需查看最新的绝对覆盖率报告,请点击此处。 此信息中心以源代码树的子集显示已发现的所有测试已涵盖的所有代码的树布局。您可以按目录结构浏览该树,查看目录的总覆盖率指标或文件的各个行覆盖率信息。

此外,覆盖率信息还会作为 Google 内部代码搜索中的图层提供。

“覆盖率”信息中心屏幕截图

增量测试覆盖率

增量测试覆盖率显示在 Gerrit 代码审核网页界面上发生更改的上下文中。增量覆盖率可以显示(尤其是在给定更改的上下文中),哪些修改的代码行包含在测试中,哪些修改的代码行没有。

增量测试覆盖率由 Fuchsia 的提交队列 (CQ) 基础架构收集。将更改发送到 CQ (Commit-Queue+1) 时,您可以点击“检查”标签页,然后在三点状菜单下点击“显示更多结果”,然后在过滤条件文本框中输入“fuchsia-coverage”以查找负责收集增量覆盖率的 tryjob,并在 Gerrit 中显示。此 tryjob 完成后,您的补丁集应具有绝对覆盖率 (|Cov.|) 和增量覆盖率 (ΔCov.)

针对影响项目的更改保持较高的增量测试覆盖率有助于持续保持较高的测试覆盖率。特别是,它可以防止在项目中引入未经测试的新代码。变更作者可以查看变更的增量覆盖率信息,以确保其测试覆盖率足够高。代码审核人员可以查看关于变更的增量测试覆盖率信息,并要求作者填补他们认为重要的任何测试缺口。

显示覆盖率统计信息的 Gerrit 屏幕截图

显示行覆盖率的 Gerrit 屏幕截图

以覆盖率为导向的开发工作流

您可以在浏览器或 VS Code 中查看本地修改的覆盖率。 您可以使用此库来建立以覆盖率为导向的开发工作流。

准备测试环境

首先,我们将 build 配置为使用覆盖率变体,并在其中添加演示该工作流的示例。

C++

fx set core.x64 --variant coverage --with examples/hello_world
fx build

Rust

fx set core.x64 --variant coverage-rust --with examples/hello_world
fx build

让我们启动一个将作为目标设备的模拟器,然后启动一个更新服务器。在此步骤中,我们将使用两个终端。如果您已有正在运行的目标设备(无论是模拟器还是真实的硬件),则可以跳过此步骤。

在您的第一个终端中:

fx qemu -kN

接下来,启动将用于发布测试软件包更新的软件包服务器,此过程将在后台运行。如果您已有软件包服务器正在运行,则可以跳过此步骤。

在第二个终端中:

fx serve

在浏览器中查看覆盖率

在此工作流中,我们将运行测试并生成可在浏览器中查看的覆盖率报告。

执行测试并导出覆盖率 HTML 报告

我们执行测试并生成 HTML 报告。

C++

fx coverage --html-output-dir $HOME/fx_coverage hello-world-cpp-unittests

Rust

fx coverage --html-output-dir $HOME/fx_coverage hello-world-rust-tests

在浏览器中查看覆盖率摘要

使用浏览器打开 $HOME/fx_coverage/index.html。您应该会看到覆盖率摘要页面。

覆盖率报告屏幕截图

点击任一文件即可查看该文件的行覆盖率。“计数”列显示的是某行在测试中被访问的次数(1 次或多次)。“Count”没有值表示为 0,即未覆盖该线条。

在 VS Code 中查看覆盖率

请务必先准备好测试环境,然后再开始学习本部分内容。

  1. 从 Visual Studio Marketplace 安装 coverage-gutters 扩展程序。
  2. 通过将以下属性添加到 settings.json 来配置覆盖范围边线。
{
    "coverage-gutters.coverageBaseDir": ".",
    "coverage-gutters.showLineCoverage": true,
    "coverage-gutters.coverageFileNames": [ "lcov.info" ]
}

运行测试并查看覆盖率

让我们执行测试并导出 LCOV 文件,VS Code 将使用该文件显示覆盖率。

C++

fx coverage --lcov-output-path $FUCHSIA_DIR/lcov.info hello-world-cpp-unittests

Rust

fx coverage --lcov-output-path $FUCHSIA_DIR/lcov.info hello-world-rust-tests

在 VS Code 中查看覆盖率

  1. 找到您要查看覆盖率的文件。
  2. 右键点击文件的修改区域,然后选择“Coverage Gutters: Display Coverage”。
  3. 覆盖的线条用绿色表示,未覆盖的线条用红色表示。
  4. 您可以重新导出 LCOV 并重新执行第 2 步,以查看更新后的覆盖率(出于某种原因,“监视”功能不起作用)。

展示广告覆盖率

VS Code 覆盖率

针对更改重新运行测试

最后,您可以使用此命令监控文件系统更改,并在每次保存代码时重新运行测试。

C++

fx -i coverage --lcov-output-path $FUCHSIA_DIR/lcov.info hello-world-cpp-unittests

Rust

fx -i coverage --lcov-output-path $FUCHSIA_DIR/lcov.info hello-world-rust-tests

端到端 (E2E) 测试排除项

只有单元测试和封闭集成测试被视为可靠的测试覆盖率来源。对于端到端测试,系统不会收集或提供测试覆盖率。

端到端测试是一种大型系统测试,它们会将产品作为一个整体运用,不一定要涵盖源代码中明确定义的部分。例如,Fucsia 上的 E2E 测试通常在模拟器中启动系统,与系统进行交互,并预期会出现某些行为。

原因

由于端到端测试会将系统作为一个整体进行运作:

  • 据观察,它们经常会在两次运行之间触发不同的代码路径,导致其覆盖率结果不稳定。
  • 它们经常超时,会导致覆盖率构建器出现故障。与单元测试和小型集成测试相比,端到端测试的运行速度要慢得多,这些测试通常需要几分钟才能完成。此外,它们在覆盖率构建器上的运行速度甚至更慢,因为覆盖率开销会降低性能。

具体做法

对于 core 等顶级 buildbot 软件包,系统会提供对应的 core_no_e2e,以便收集覆盖率的聊天机器人可以使用 no_e2e 软件包,以避免构建和运行任何端到端测试。

目前,没有可靠的方式来识别树内所有端到端测试。作为代理,no_e2e 软件包通过 GN 的 assert_no_deps 保持其递归依赖项中没有任何已知的 e2e 测试库这一不变。E2E 测试库列表经过手动挑选和维护,并假设更改频率非常低:

e2e_test_libs = [
  "//sdk/testing/sl4f/client",
  "//third_party/mobly($host_toolchain)",
  "//src/testing/end_to_end/honeydew($host_toolchain)",
]
if (is_linux) {
  e2e_test_libs += [ "//tools/emulator($host_toolchain)" ]
}

限制

目前,我们仅会在以下情况下收集测试覆盖率:

  • 代码采用 C、C++ 或 Rust 编写。
  • 代码以用户模式在 Fuchsia 上运行,或在主机上运行。尚不支持内核覆盖率(跟踪 bug)。
  • 该测试在 qemu 上运行。尚不支持在硬件上进行测试。
  • 该测试会作为 core 产品配置的一部分运行。
  • 不支持端到端 (e2e) 测试。

最后要注意的是,e2e 测试会在整个系统中执行大量代码,但它们执行的方式在各次运行之间不一致(或不稳定)。要实现更高的代码测试覆盖率,您可以使用单元测试和集成测试来实现,事实上,我们也建议使用。

实验功能

默认情况下,增量覆盖率仅在 core.x64 中收集。如需收集 core.x64core.arm64 中所做更改的组合覆盖率,请按以下步骤操作:

  1. 在 Gerrit 中,转到“Checks”标签页。
  2. 按“Choose tryjobs”。
  3. 添加了 fuchsia-coverage-x64-arm64

在 Gerrit 中手动选择 x64-arm64 覆盖率

系统会显示一个对勾。从待处理状态变为完成状态后,刷新 Gerrit 以查看覆盖率结果。

另请参阅:问题 91893:仅针对 x64 收集 Gerrit 中的增量覆盖率

即将发布的功能

我们正在开发对以下其他用例的支持:

  • 内核代码覆盖率。
  • 涵盖 core 以外的产品配置,例如 bringupworkstation_eng
  • 硬件目标的覆盖率,即从不在 Qemu 上运行的测试中收集数据。

问题排查

不受支持的配置 / 语言 / 运行时

如果您没有看到代码的绝对覆盖率或增量覆盖率信息,请先查看限制,并确保您的代码从一开始就按预期获得覆盖率支持。

为帮助您排查问题,请检查是否缺少覆盖率(行应具有覆盖率,但显示为未覆盖)或者您是否根本没有覆盖率信息(文件根本不会出现在覆盖率报告中,或者某些行没有注释到它们是否被覆盖)。

缺少覆盖率表示代码是通过插桩构建的,但运行的测试实际并未涵盖该代码。没有任何覆盖率信息可能表示您的代码未在覆盖率下构建,或者您的测试未在覆盖率下运行(详见下文)。

过时报告 / 延迟时间

绝对覆盖率报告是在合并代码后生成的,可能需要几个小时才能完全编译。信息中心会显示所生成报告的提交哈希值。如果您在信息中心没有看到预期的结果,请确保生成的数据已经过任何可能影响覆盖率的近期更改。如果数据似乎已过时,请稍后回来刷新页面。

增量覆盖率报告由 CQ 生成。请确保您查看的是已发送到 CQ 的补丁集。您可以点击“显示实验性 tryjobs”以显示名为 fuchsia-coverage 的 tryjob。如果 tryjob 仍在运行,请稍后返回并刷新页面。

确保您的测试已运行

如果您的代码缺少预计的覆盖率,请选择应该覆盖您的代码的测试,并确保它在覆盖率 tryjob 上运行。

  1. 在 Gerrit 中查找 tryjob,或在 CI 信息中心查找最近运行的 fuchsia-coverage
  2. 在“概览”标签页中,找到“收集 build”步骤并展开该步骤,即可找到指向针对不同配置显示不同覆盖率构建和测试运行的页面的链接。
  3. 其中每个页面都应有一个“Test Results”标签页,显示运行的所有测试。确保已运行预期的测试,最好通过测试。

如果您的测试未按预期针对任何覆盖范围 tryjob 运行,原因可能只是因为测试仅在 CI/CQ 当前未涵盖的配置中运行。另一个是,测试在覆盖率变体中被明确停用。例如,引用您的测试的 BUILD.gn 文件可能如下所示:

fuchsia_test_package("foo_test") {
  test_components = [ ":test" ]
  deps = [ ":foo" ]

  # TODO(https://fxbug.dev/42074368): This test is intentionally disabled on coverage.
  if (is_coverage) {
    test_specs = {
      environments = [
        {
          dimensions = emu_env.dimensions
          tags = [ "disabled" ]
        },
      ]
    }
  }
}

请查看相关背景信息,了解测试在覆盖率下被停用的原因,并进行调查。

此处的示例介绍了如何排查测试因未设置为在 CQ 上运行而未显示覆盖率的情况。

仅测试失败或覆盖率不稳定

与上述内容相关,在覆盖率下,测试更容易出现不稳定问题示例。在运行时收集覆盖率所产生的额外开销会降低性能,这反过来又会影响时间,这通常是导致出现额外稳定性的原因。

另一个原因可能是测试期间在常规测试运行中未遇到超时。实验结果表明,由于收集运行时配置文件会产生额外的开销,因此覆盖率测试的运行速度平均会慢至 2.3 倍。为了解决这个问题,测试运行程序在运行覆盖率 build 时会为每项测试提供更长的超时时间。不过,测试可能仍会有自己的内部操作超时时限,这可能会受此影响。

一般来说,测试中不应出现超时。在测试中等待异步操作时,最好无限期地等待,并让测试运行程序的总体超时过期。

最后,关于覆盖率变体组件,可以使用 fuchsia.debug.DebugData 协议。这会干扰对组件具体使用哪些功能做出假设的测试。请参阅以下示例:

一种即时解决方法是停用覆盖率测试(请参阅上面的 GN 代码段),代价是立即不从测试中收集覆盖率信息。最佳实践是,您应该像处理其他地方一样处理覆盖率碎片,主要是解决不稳定问题。

另请参阅:不稳定的测试政策

Gerrit 中的覆盖范围没有达到预期

如果您没有看到某些行的覆盖率,但您确信正在运行的测试应该覆盖这些行,请尝试按“Choose Tryjobs”以重新收集这些行,找到 fuchsia-coverage 并添加它。

添加紫红色覆盖效果的 Gerrit 屏幕截图

如果 fuchsia-coverage 完成(变为绿色),并且您看到不同的线路覆盖率结果,则表示以下某种情况为 true:

  1. 您的测试在不同运行之间以不一致的方式执行被测代码。这通常也会导致测试结果不稳定,并且通常是测试或被测代码的行为存在问题。
  2. 覆盖率的生成和收集方式存在问题,会导致结果不一致。请提交错误。

测试覆盖率的运作方式

Fuchsia 的代码覆盖率 build、测试运行时支持和处理工具使用 LLVM 基于源代码的代码覆盖率。camera-rt 配置文件运行时支持 Fuchsia 平台。

选择 "coverage" build 变体时,系统会激活配置文件插桩。然后,编译器会生成计数器(每个计数器都对应于代码控制流中的一个分支),并在分支条目上发出指令以递增关联的计数器。此外,配置文件插桩运行时库会关联到可执行文件。

如需了解实现详情,请参阅 LLVM 代码覆盖率映射格式

请注意,插桩会导致二进制文件大小增加、内存使用量增加并减慢测试执行时间。为弥补此问题,我们采取了一些措施:

  • 配置文件变体中的测试的超时时间更长。
  • 配置文件变体中的测试在编译时进行了一些优化。
  • 覆盖率目前在存储空间不受限制的模拟器上运行。
  • 对于增量覆盖率,系统只会对受更改影响的来源进行插桩测试。

Fuchsia 上的配置文件运行时库将配置文件数据存储在 VMO 中,并使用 fuchsia.debug.DebugData 协议发布 VMO 的句柄。此协议在运行时使用组件框架可供测试使用,并由测试运行程序框架的设备端控制器测试管理器托管。

系统会在测试领域终止后收集配置文件,以及其中托管的任何组件。然后,这些配置文件会处理成测试的单个摘要。这是一项重要的优化措施,可以大幅缩减资料的总大小。然后,系统会将优化后的配置文件发送到主机端测试控制器。

主机使用 covargs 工具,该工具本身使用 llvm-profdatallvm-cov 工具将原始配置文件转换为摘要格式,并生成测试覆盖率报告。此外,covargs 还会将数据转换为 protobuf 格式,这种格式用作与各种信息中心的交换格式。

路线图

日常工作:

  • 改进了覆盖率运行时的性能和可靠性。
  • 对 ZBI 测试中的源代码覆盖率的内核支持。
  • 自定义覆盖率信息中心和提醒:为您的团队构建信息中心。
  • 本地工作流:在本地运行测试,在本地生成覆盖率报告。
  • IDE 集成:在 VS Code 中查看覆盖层。

近期作业:

  • 树外支持:Fuchsia CI/CQ 之外支持。

深入阅读