设计初衷
软件测试是一种常见做法,可帮助团队持续交付高质量的代码。测试可针对软件行为执行不变量,捕获并防止功能或其他所需属性出现回归问题,并有助于扩展工程流程。
以源代码行覆盖率来衡量测试覆盖率有助于工程师发现测试解决方案中的不足之处。将测试覆盖率作为一种指标,有助于提高软件质量并采用更安全的开发实践。持续衡量测试覆盖率有助于工程师保持高标准的代码质量。
测试覆盖率并不能保证代码没有 bug。测试应与其他工具(例如模糊测试、静态和动态分析等)搭配使用。
绝对测试覆盖率
绝对测试覆盖率是指完整测试集覆盖的所有源代码行的比例。Fuchsia 的持续集成 (CI) 基础架构会生成绝对覆盖率报告,并不断刷新这些报告。覆盖率报告通常最多延迟几个小时。
绝对覆盖率信息中心
如需查看最新的绝对覆盖率报告,请点击此处。 此信息中心以树状布局显示了所有被执行的测试所覆盖的所有代码(作为源代码树的子集)。您可以按目录结构浏览树状视图,并查看目录的总覆盖率指标或文件的单行覆盖率信息。
此外,覆盖率信息还可作为 Google 内部代码搜索中的一个层提供。

增量测试覆盖率
增量测试覆盖率显示在 Gerrit 代码审核网页界面中的更改上下文中。增量覆盖率会显示,在给定更改的上下文中,哪些修改后的行被测试覆盖,哪些修改后的行未被测试覆盖。
增量测试覆盖率由 Fuchsia 的提交队列 (CQ) 基础架构收集。向 CQ (Commit-Queue+1) 发送更改时,您可以点击“检查”标签页,然后在三点状菜单下点击“显示其他结果”,接着在过滤条件文本框中输入“fuchsia-coverage”,找到负责收集增量覆盖率以在 Gerrit 中显示的 tryjob。此 tryjob 完成后,您的补丁集应具有绝对覆盖率 (|Cov.|) 和增量覆盖率 (ΔCov.)
针对影响项目的更改保持较高的增量测试覆盖率有助于持续保持较高的测试覆盖率。特别是,它可以防止将未经测试的新代码引入到项目中。更改作者可以查看有关其更改的增量覆盖率信息,以确保其测试覆盖率足够。代码审核者可以查看有关更改的增量测试覆盖率信息,并要求作者弥合他们认为重要的任何测试缺口。


覆盖率驱动型开发工作流
您可以在浏览器或 VS Code 中查看本地编辑的覆盖率。 您可以使用此功能来建立覆盖率驱动的开发工作流。
准备测试环境
首先,我们来配置 build 以使用覆盖率变体,并包含我们将用于演示工作流的示例。
C++
fx set core.x64 --variant coverage --with examples/hello_world --include-clippy=false
fx buildRust
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-unittestsRust
fx coverage --html-output-dir $HOME/fx_coverage hello-world-rust-tests在浏览器中查看覆盖情况摘要
在浏览器中打开 $HOME/fx_coverage/index.html。您应该会看到一个覆盖率摘要页面。

点击任何文件都会显示该文件的行覆盖率。“次数”列显示了在测试中访问某行的次数(1 次或更多次)。“数量”没有值表示 0,即相应行未被覆盖。
在 VS Code 中查看覆盖范围
只有在准备好测试环境后,才能开始本部分。
- 从 Visual Studio Marketplace 安装 coverage-gutters 扩展程序。
- 通过向
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-unittestsRust
fx coverage --lcov-output-path $FUCHSIA_DIR/lcov.info hello-world-rust-tests在 VS Code 中查看覆盖范围
- 找到您要查看覆盖率的文件。
- 右键点击文件的编辑区域,然后选择“Coverage Gutters: Display Coverage”。
- 已覆盖的行以绿色显示,未覆盖的行以红色显示。
- 您可以重新导出 LCOV 并重新执行第 2 步,以查看更新后的覆盖率(由于某种原因,“watch”不起作用)。


针对变更重新运行测试
最后,您可以使用此命令监控文件系统更改,并在每次保存代码时重新运行测试。
C++
fx -i coverage --lcov-output-path $FUCHSIA_DIR/lcov.info hello-world-cpp-unittestsRust
fx -i coverage --lcov-output-path $FUCHSIA_DIR/lcov.info hello-world-rust-tests端到端 (E2E) 测试排除
只有单元测试和封闭式集成测试才被视为可靠的测试覆盖率来源。系统不会收集或显示端到端测试的测试覆盖率。
E2E 测试是大型系统测试,可对整个产品进行测试,但不一定涵盖源代码中明确定义的部分。例如,Fuchsia 上的 E2E 测试通常会在模拟器中启动系统,与系统互动,并预期某些行为。
原因
由于 E2E 测试会全面测试系统:
- 我们发现,它们在运行之间经常触发不同的代码路径,导致其覆盖率结果不稳定。
- 它们经常在覆盖面构建器上超时,导致构建器不稳定。E2E 测试的运行速度比单元测试和小规模集成测试慢得多,通常需要几分钟才能完成。在覆盖率 build 中,由于覆盖率开销会降低性能,因此运行速度会更慢。
方式
对于 core 等顶级 buildbot bundle,系统会提供对应的 core_no_e2e,以便收集覆盖率的机器人可以使用 no_e2e bundle 来避免构建和运行任何 E2E 测试。
目前,还没有可靠的方法来识别树中的所有 E2E 测试。作为代理,no_e2e 软件包通过 GN 的 assert_no_deps 保持了这样一种不变量:它们的递归依赖项中没有任何已知的端到端测试库。E2E 测试库列表是手动整理和维护的,假设它很少更改:
e2e_test_libs = [
"//sdk/testing/sl4f/client",
"//third_party/mobly($host_toolchain)",
"//src/testing/end_to_end/antlion($host_toolchain)",
"//src/testing/end_to_end/honeydew($host_toolchain)",
]
# For libraries only supported on Linux hosts, we must use host_os to determine
# whether to include them. The is_linux condition will not function as naively expected
# because it applies to the current toolchain which may not be a host toolchain
# when this file is loaded.
if (host_os == "linux") {
e2e_test_libs += [ "//tools/emulator($host_toolchain)" ]
}
限制
目前,仅在以下情况下收集测试覆盖率:
- 代码采用 C、C++ 或 Rust 编写。
- 代码在 Fuchsia 上以用户模式运行,或在主机上运行。尚不支持内核覆盖率(跟踪 bug)。
- 测试在 qemu 上运行。尚不支持在硬件上进行测试。
- 测试作为
core产品配置的一部分运行。 - 不支持端到端 (e2e) 测试。
最后一点,端到端测试会运行系统中的大量代码,但运行方式在不同运行之间不一致(或“不稳定”)。为了提高代码的测试覆盖率,建议使用单元测试和集成测试来实现这一目标。
实验功能
默认情况下,增量覆盖率仅在 core.x64 中收集。如需收集 core.x64 和 core.arm64 中更改的合并覆盖率,请按以下步骤操作:
- 在 Gerrit 中,前往“检查”标签页。
- 按“选择 Tryjobs”。
- 添加
fuchsia-coverage-x64-arm64。

系统会显示一个对勾。当状态从“待处理”变为“已完成”后,刷新 Gerrit 以查看覆盖率结果。
另请参阅: 问题 91893:Gerrit 中仅针对 x64 收集增量覆盖率
即将发布的功能
我们目前正在开发对以下其他用例的支持:
- 内核代码覆盖率。
core以外的产品配置的覆盖率,例如bringup或workstation_eng。- 硬件目标平台的覆盖率,即从不在 QEMU 上运行的测试中收集的覆盖率。
问题排查
不支持的配置 / 语言 / 运行时
如果您没有看到代码的绝对覆盖率或增量覆盖率信息,请先查看限制,并确保您的代码本来就应该获得覆盖率支持。
为帮助您排查问题,请检查是否存在以下情况:缺少覆盖率(代码行应具有覆盖率,但显示为未覆盖)或根本没有覆盖率信息(文件根本未显示在覆盖率报告中,或者代码行未注释为是否已覆盖)。
缺少覆盖率表示代码已通过插桩构建,但实际上并未被运行的测试覆盖。完全没有覆盖率信息可能表示您的代码未构建覆盖率,或者您的测试未在覆盖率下运行(下文会详细介绍)。
报告过时 / 延迟
绝对覆盖率报告会在代码合并后生成,可能需要几个小时才能完全编译。信息中心会显示所生成报告的提交哈希值。如果您在信息中心内没有看到预期结果,请确保生成的数据是在最近影响覆盖面的任何更改之后。如果数据似乎已过时,请稍后返回并刷新页面。
增量覆盖率报告由 CQ 生成。确保您查看的是已发送到 CQ 的补丁集。您可以点击“show experimental tryjobs”来显示名为 fuchsia-coverage 的 tryjob。如果 tryjob 仍在运行,请稍后返回并刷新页面。
确保测试已运行
如果您的代码缺少您期望看到的覆盖率,请选择本应覆盖您的代码的测试,并确保该测试已在覆盖率 tryjob 上运行。
- 在 Gerrit 中找到 tryjob,或在 CI 信息中心中找到最近的
fuchsia-coverage运行。 - 在“概览”标签页中,找到“收集 build”步骤并展开,以找到指向以下页面的链接:这些页面会显示不同配置的不同覆盖率 build 和测试运行。
- 每个页面都应有一个“测试结果”标签页,其中显示了运行的所有测试。确保运行了预期测试,最好是测试通过。
如果您的测试未按预期在任何覆盖率 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 并添加它。

如果 fuchsia-coverage 完成(变为绿色),但您看到的代码覆盖率结果不同,则以下情况之一为真:
- 您的测试在不同运行之间以不一致的方式执行被测代码。这通常也会导致不可靠的测试结果,并且通常是测试行为或被测代码存在问题。
- 覆盖率的生成和收集方式存在问题,导致结果不一致。请提交错误。
测试覆盖率的运作方式
Fuchsia 的代码覆盖率 build、测试运行时支持和处理工具使用 LLVM 基于源代码的代码覆盖率。Fuchsia 平台由 compiler-rt 配置文件运行时支持。
选择 "coverage" build 变体时,系统会激活配置文件插桩。然后,编译器将生成与代码控制流中的每个分支对应的计数器,并发出有关分支入口的指令,以递增关联的计数器。此外,配置文件插桩运行时库会链接到可执行文件中。
如需了解更多实现细节,请参阅 LLVM 代码覆盖率映射格式。
请注意,插桩会导致二进制文件大小增加、内存用量增加,并延长测试作业执行时间。我们采取了一些措施来抵消这种影响:
- 配置文件变体中的测试可获得更长的超时时间。
- 配置文件变体中的测试会经过一些优化后进行编译。
- 覆盖率目前在存储空间限制较少的模拟器上运行。
- 对于增量覆盖率,仅对受更改影响的来源进行插桩。
Fuchsia 上的配置文件运行时库将配置文件数据存储在 VMO 中,并使用 fuchsia.debug.DebugData 协议发布指向该 VMO 的句柄。此协议在运行时通过 Component Framework 提供给测试,并由 Test Runner Framework 的设备端控制器 Test Manager 托管。
在测试 realm 终止后,系统会收集这些配置文件以及其中托管的所有组件。然后,系统会将这些个人资料处理成测试的单个摘要。这是一项重要的优化,可显著减小配置文件的总大小。然后,优化后的配置文件会发送到宿主端测试控制器。
主机使用 covargs 工具(该工具本身使用 llvm-profdata 和 llvm-cov 工具)将原始配置文件转换为摘要格式,并生成测试覆盖率报告。此外,covargs 会将数据转换为 protobuf 格式,该格式用作与各种信息中心的数据交换格式。
路线图
持续性工作:
- 改进了覆盖面运行时的性能和可靠性。
- 针对 ZBI 测试的源代码覆盖率的内核支持。
- 自定义覆盖面信息中心和提醒:为您的团队构建信息中心。
- 本地工作流程:在本地运行测试,在本地生成覆盖率报告。
- IDE 集成:在 VS Code 中查看覆盖率层。
近期作业:
- 树外支持:Fuchsia CI/CQ 之外的覆盖范围。