测试和测试套件

概览

本页介绍了在 BUILD.bazel 文件中定义和构建测试和 test_suite() 目标的样式和最佳实践,以及如何将它们连接到 GN build 图。本页面上的指南是对常见 Bazel 样式指南和最佳实践以及 BUILD.bazel 文件样式指南的补充。如需了解如何定义、构建和运行主机和设备测试目标,请参阅 Bazel 测试。

本页面是 Bazel 样式指南和最佳实践的一部分。

测试套件结构

在 Fuchsia 的 Bazel 构建图中,测试分为三层,并与构建相关联:

  1. 单个软件包:每个 BUILD.bazel 文件都会定义私有 *_test 目标,并通过名为 "tests" 和/或 "host_tests" 的软件包 test_suite() 目标公开这些目标(请参阅在单个软件包中定义测试和测试套件)。

  2. 汇总软件包:针对某个区域或子系统组的专用软件包,可将软件包 test_suite() 目标从单个软件包分组到汇总 test_suite() 目标(例如 "bluetooth_tests")。

  3. GN build 图集成:虽然 GN 驱动整体 build,但 BUILD.gn 中的 bazel_test_suite() 目标会引用同一目录中的聚合 test_suite() 目标,以便将测试包含在 tests.json 中(请参阅将 Bazel 测试套件连接到 GN build 图)。

不得在定义单个测试的软件包之外引用这些测试,并且软件包不会以递归方式包含其子软件包中的测试或 test_suite() 目标。如需了解此结构背后的基本原理,请参阅为什么只公开 test_suite() 目标?

示例

//src/connectivity/bluetooth/lib/packet-encoding/BUILD.bazel(单独的软件包):

test_suite(
    name = "tests",
    tests = [],  # Include all tests in the package.
    visibility = ["//src/connectivity/bluetooth:__pkg__"],
)

# Private test target(s) automatically included in ":tests" above.
...

//src/connectivity/bluetooth/BUILD.bazel(聚合软件包):

test_suite(
    name = "bluetooth_tests",
    tests = [
        "//src/connectivity/bluetooth/lib/packet-encoding:tests",
        ...
    ],
)

//src/connectivity/bluetooth/BUILD.gn(GN 集成):

import("//build/bazel/bazel_test_suite.gni")

bazel_test_suite("bazel_tests") {
  target_tests = [ "//src/connectivity/bluetooth:bluetooth_tests" ]
}

group("tests") {
  testonly = true
  deps = [
    ":bazel_tests",
    ...
  ]
}

定义和构建 Bazel 测试和测试套件

目标名称和可见性

  • 目标名称:测试目标的名称始终以 "_test" 结尾。Bazel test_suite() 目标 的名称应为 或 ,或以 "tests" 或 "host_tests" 结尾(视情况而定)。

  • 测试目标可见性:不得在定义单个测试的软件包之外引用这些测试。测试目标必须始终具有软件包私有可见性,这通常是默认设置(请参阅目标可见性)。

  • test_suite() 目标可见性:test_suite() 目标应仅对包含依赖于它们的聚合 test_suite() 目标(在其 tests 属性中)的软件包可见。始终使用 :__pkg__,而不是 :__subpackages__。

  • 引用测试和 test_suite() 目标:测试和 test_suite() 目标只能由 test_suite() 目标(在其 tests 属性中)引用。请勿将其放入 filegroup() 或类似容器中。

平台和 CPU 架构兼容性

目标平台兼容性 (target_compatible_with)

测试目标可以且应该酌情指定 target_compatible_with(例如,在主机测试中指定 target_compatible_with = HOST_OS_CONSTRAINTS;请参阅 target_compatible_with)。

不过,请勿依赖 Bazel 测试兼容性来过滤测试目标并仅运行兼容的目标。具体而言,请勿将针对不同平台的测试分组到单个 test_suite() 中(或将包含针对不同平台的测试的 test_suite() 目标分组到单个 test_suite() 中)。

test_suite() 目标是否应指定 target_compatible_with 待定。

CPU 架构

尽可能避免为每个 CPU 架构等定义单独的测试或 test_suite() 目标。理想情况下,可以针对所有 CPU 架构构建单个测试目标。

在极少数情况下,可能会有特定于 CPU 架构的测试,例如针对用汇编语言编写的 SIMD 库的单元测试。在这种情况下,请向 Build 团队寻求指导。(一旦我们对这些场景有了一些经验,就可以更新此文本。)

在各个软件包中定义测试和测试套件

公开软件包 test_suite() 目标

每个 Bazel 软件包(BUILD.bazel 文件)都应公开一个或多个软件包 test_suite() 目标,这些目标依赖于该软件包中定义的测试(在单个 BUILD.bazel 文件中,而不是在其任何子软件包中)。

这些目标应命名为 "tests" 或 "host_tests",具体取决于目标平台。所有此类 test_suite() 目标都应是 BUILD.bazel 文件中的第一个目标,最好紧跟在 load() 和 package() 语句之后。

包含软件包中的所有测试

Bazel 软件包中定义的所有测试都必须是同一软件包中某个 test_suite() 目标的依赖项。(未来,此要求可能会更改为“至少一个”。)这包括由用于定义非测试目标的宏定义的测试。

如果软件包中的所有测试都应位于给定的 test_suite() 中,请指定一个空的 tests 属性,并添加注释说明软件包中的所有测试都包含在内:

test_suite(
    name = "tests",
    tests = [],  # Include all tests in the package.
    visibility = [...],
)

如果可能,这种方法更易于维护,在添加测试时少了一个步骤,并可确保不会遗漏测试。

在所有软件包中定义默认 test_suite()

在可能的情况下,所有 BUILD.bazel 文件都应具有名为 "tests" 或 "host_tests" 且具有空 tests 属性 (tests = []) 的 test_suite(),无论它们是否明确定义了测试。每个此类 test_suite() 都应从适当的聚合 test_suite() 目标中引用。

这样可以确保将来添加到软件包中的任何测试(无论是显式添加还是通过宏添加)都会包含在 build 图中。如果我们能够强制执行此操作,还可以防止忘记将测试添加到 build 图中。

此规则的主要例外情况是同时包含 Fuchsia 和主机测试的软件包,因为目前无法在这两种情况下使用相同的测试目标,因此无法使用空的 tests 属性机制。

修改具有空 tests 属性的 test_suite()

修改 tests 属性 为空或未指定的现有 test_suite() 时,您必须确保该软件包 定义的所有测试都明确添加到此 或软件包中的其他 test_suite()。只有在软件包的测试在多个 test_suite() 目标之间划分时,才会发生这种情况。

不依赖于子软件包

请勿依赖软件包子软件包中的测试或 test_suite() 目标。 聚合 test_suite() 目标除外,但单个软件包通常不应同时具有这两种类型的 test_suite() 目标。

这与 GN 中的常见模式不同,在 GN 中,group("tests") 依赖于其子目录中的所有此类目标,这些目标以递归方式执行相同的操作。我们不希望在 Bazel 中重复这种模式。

聚合 test_suite() 目标

这些 test_suite() 目标会将各个软件包(例如,针对某个区域或子系统)中的 test_suite() 目标分组,并且应具有描述其范围和/或用途的名称。它们仍应视情况以 "tests" 或 "host_tests" 结尾。每种目标可能不止一个。举个假设的例子,"bluetooth_tests" 可能适用于所有产品,而 "bluetooth_le_tests" 可能包含要在支持蓝牙 LE 的产品上运行的其他测试。

一般来说,包含此类聚合 test_suite() 目标的软件包(BUILD.bazel 文件)本身不应定义其他目标,尤其是测试目标。例如://src/connectivity/bluetooth/。(另请注意,BUILD.gn 文件仅定义 group() 目标。)

根据汇总目标

未来,本部分将介绍每个产品的测试软件包中可选的进一步汇总和包含。

目前,系统会使用依赖于 bazel_test_suite() 目标的 GN 测试 group() 目标的现有依赖项。

Rust 单元测试

在 GN 中,rustc_*() 模板接受 with_unit_tests 参数,当该参数为 true 时,系统会根据源文件中的测试自动生成单元测试目标。

将 Bazel 测试套件连接到 GN build 图

使用 bazel_test_suite() GN 模板定义的目标应仅引用在同一目录(位于 BUILD.gn 文件旁边的 BUILD.bazel 文件中)中定义的聚合 test_suite() 目标。它们不得引用单个测试。

每个聚合 test_suite() 目标都必须在同一目录(位于 BUILD.bazel 文件旁边的 BUILD.gn 文件中)中由 bazel_test_suite() GN 模板定义的目标的相应字段中引用,以便其测试包含在 tests.json 中。使用以 "//" 开头的完整 Bazel 标签,而不是相对 ":" 标签(例如 "//src/connectivity/bluetooth:bluetooth_tests"),并按如下方式在 GN 模板的参数中引用目标:

  • 名称以 "host_tests" 结尾的目标:host_tests

  • 所有其他(以 "tests" 结尾):target_tests

附录:为什么只公开 test_suite() 目标?

仅公开 test_suite() 目标(而非单个测试)的优势包括:

  • visibility 只需要为 test_suite() 目标指定,而不需要为每个单独的测试目标指定。

  • 这对于支持 with_host_unit_tests 和/或 with_unit_tests 的宏(例如 Fuchsia 的 rustc_*() 和 ffx_plugin())尤为重要(请参阅 Rust 单元测试),因为测试目标的 visibility 无法独立于生产目标进行指定。如果各个测试是在 Bazel 软件包之外引用的,则非测试目标的 visibility 需要包含依赖于测试的 Bazel 软件包。

  • 聚合 test_suite() 目标中不包含存在哪些测试以及这些测试的名称。

  • 在添加、重命名或移除测试时,包含汇总 test_suite() 目标的软件包无需获得 OWNERS 批准。

  • 与在树中其他位置的一个(或多个)test_suite() 目标相比,在同一 BUILD.bazel 文件中,有人更有可能记得更新 test_suite() 目标。

  • 在某些情况下,可以通过指定空的 tests 属性来自动处理测试包含(请参阅上文中的在各个软件包中定义测试和测试套件)。