Fuchsia 测试运行程序框架

借助 Fuchsia 组件框架,开发者可以 各种语言和运行时。Fuchsia 自己的代码使用多种多样的 组件编程语言,包括 C/C++、Rust、Dart 和 Go。

测试运行程序框架使用组件框架运行程序作为 各种测试运行时和通用 Fuchsia 协议之间的集成层 来启动测试并接收结果。这就使得 一方面,可以让开发者将自己的语言和测试环境 另一方面,它允许构建和测试 Fuchsia 并针对不同硬件开发应用。

测试管理器

test_manager 组件负责在 Fuchsia 上运行测试 设备。测试管理器公开 fuchsia.test.manager.RunBuilder 协议, 以及发布测试套件

每个测试套件都作为测试管理器的子项启动。提供测试套件 测试经理所提供的这些功能, 保持测试与系统其余部分的隔离例如 封闭测试具有记录消息的功能,但不具有 与沙盒外部的真实系统资源交互的功能。 测试管理器仅使用测试领域的一项功能,即控制器协议 测试套件公开的代码这样做是为了确保封闭性(测试结果不会 和隔离(测试 不会相互影响或系统其余部分)。

测试管理器控制器本身提供给系统中的其他组件 以便将测试作业与各种开发者工具集成。测试 然后使用 fx testffx 等工具启动。

测试套件协议

测试套件协议 fuchsia.test.Suite 由 测试经理来控制测试,例如调用测试用例和收集 结果。

测试作者通常不需要实现此协议。相反,它们依赖于 在测试运行程序上为他们执行此操作。例如,您可以 使用 GoogleTest 框架用 C++ 编写一个测试,然后使用 gtest_runner(在组件清单中) 以便与测试运行程序框架集成。

测试运行程序

包含语言和运行时的框架

测试运行程序是测试运行程序框架和常见测试运行程序之间可重复使用的适配器 语言和供开发者用来编写测试的各种框架他们会实施 fuchsia.test.Suite 协议(代表测试作者) 可让开发者针对其语言和框架编写惯用测试 选择。

您可以生成简单单元测试的组件清单 符合构建规则为 v2 测试生成的组件清单将包括 适当的测试运行程序。例如, 依赖于 GoogleTest 库的可执行文件将包含 GoogleTest runner 进行测试。

测试运行程序清单

以下测试运行程序目前已正式发布:

GoogleTest 运行程序

使用 GoogleTest 框架以 C/C++ 编写的测试的运行程序。 使用此测试编写使用 GoogleTest 编写的所有测试。

支持常用的 GoogleTest 功能,例如停用测试、运行 只能指定指定的测试、多次运行同一测试,等等。 从测试中捕获标准输出、标准错误和日志。

为了使用此运行程序,请将以下内容添加到组件清单中:

{
    include: [ "//src/sys/test_runners/gtest/default.shard.cml" ]
}

默认情况下,GoogleTest 测试用例会依次运行(一次一个测试用例)。

GoogleTest (Gunit) 运行程序

使用 GUnit 框架以 C/C++ 编写的测试的运行程序。 对于使用 GoogleTest 的 gUnit 变种编写的所有测试,都使用此方法。

支持常用的 GoogleTest 功能,例如停用测试、运行 只能指定指定的测试、多次运行同一测试,等等。 从测试中捕获标准输出、标准错误和日志。

为了使用此运行程序,请将以下内容添加到组件清单中:

{
    include: [ "sys/testing/gunit_runner.shard.cml" ]
}

默认情况下,测试用例会依次运行(一次一个测试用例)。

Rust 运行程序

使用 Rust 编程语言编写并遵循 Rust 编写的测试的运行程序 测试习语 对于所有惯用的 Rust 测试(即使用设置了 属性 [cfg(test)])。

支持常见的 Rust 测试功能,例如停用测试、运行 只能指定指定的测试、多次运行同一测试,等等。 从测试中捕获标准输出、标准错误和日志。

为了使用此运行程序,请将以下内容添加到组件清单中:

{
    include: [ "//src/sys/test_runners/rust/default.shard.cml" ]
}

默认情况下,Rust 测试用例会并行运行,一次最多 10 个用例。

Go 测试运行程序

使用 Go 编程语言编写的遵循 Go 编写的测试的运行程序 测试习语 对于使用 import "testing" 以 Go 编写的所有测试,使用此方法。

支持常用的 Go 测试功能,例如停用测试、运行 只能指定指定的测试、多次运行同一测试,等等。 从测试中捕获标准输出、标准错误和日志。

为了使用此运行程序,请将以下内容添加到组件清单中:

{
    include: [ "//src/sys/test_runners/gotests/default.shard.cml" ]
}

默认情况下,Go 测试用例会并行运行,一次最多 10 个用例。

ELF 测试运行程序

最简单的测试运行程序 - 它等待程序终止,然后报告 如果程序返回 0 或者任何错误显示失败,则表示测试已通过。 非零返回值。

如果您的测试是作为 ELF 程序实现的(例如, 使用 C/C++ 编写的可执行文件),但它不使用通用测试框架 (由现有运行程序支持)且您不愿意实现 测试运行程序。

为了使用此运行程序,请将以下内容添加到组件清单中:

{
    include: [ "sys/testing/elf_test_runner.shard.cml" ]
}

如果您使用树内单元测试 GN 模板, 您尚未使用带有专用测试运行程序的测试框架, 将以下内容添加到您的构建依赖项中:

fuchsia_unittest_package("my-test-packkage") {
    // ...
    deps = [
        // ...
        "//src/sys/testing/elftest",
    ]
}

控制测试用例的并行执行

使用 fx test 启动测试时,它们可以按顺序运行每个测试用例,或者 并行运行多个测试用例,直到达到给定的限制。默认 并行行为由测试运行程序决定。要手动控制 要并行运行的测试用例的数量,请使用测试规范:

fuchsia_test_package("my-test-pkg") {
  test_components = [ ":my_test_component" ]
  test_specs = {
    # control the parallelism
    parallel = 10
  }
}

多次运行测试

如需多次运行测试,请使用:

 fx test --count=<n> <test_url>

如果迭代超时,则不会执行进一步的迭代。

传递参数

您可以使用 fx test 向测试传递自定义参数:

fx test <test_url> -- <custom_args>

单个测试运行程序对这些自定义标志有限制:

GoogleTest 运行程序

请注意以下已知的行为变更:

--gtest_break_on_failure - 请改用:

fx test --break-on-failure <test_url>

以下标志受到限制,如果以 fuchsia.test.Suite 提供了等效的功能来取代这些测试。

  • --gtest_filter - 改为使用以下代码:
 fx test --test-filter=<glob_pattern> <test_url>

可以多次指定 --test-filter。符合以下任一项的测试: 指定的 glob 模式。

  • --gtest_also_run_disabled_tests - 改为使用以下代码:
 fx test --also-run-disabled-tests <test_url>
  • --gtest_repeat - 请参阅多次运行测试
  • --gtest_output - 不支持发出 gtest json 输出。
  • --gtest_list_tests - 不支持列出测试用例。

GoogleTest (Gunit) 运行程序

请注意以下已知的行为变更:

--gunit_break_on_failure - 请改用:

fx test --break-on-failure <test_url>

以下标志受到限制,如果以 fuchsia.test.Suite 提供了等效的功能来取代这些测试。

  • --gunit_filter - 请使用:
 fx test --test-filter=<glob_pattern> <test_url>

可以多次指定 --test-filter。符合以下任一项的测试: 指定的 glob 模式。

  • --gunit_also_run_disabled_tests - 改为使用以下代码:
 fx test --also-run-disabled-tests <test_url>
  • --gunit_repeat - 请参阅多次运行测试
  • --gunit_output - 不支持发出 gtest json/xml 输出。
  • --gunit_list_tests - 不支持列出测试用例。

Rust 运行程序

以下标志受到限制,如果以 fuchsia.test.Suite 提供了等效的功能来取代这些测试。

  • &lt;test_name_matcher&gt; - 改为使用以下代码:
 fx test --test-filter=<glob_pattern> <test_url>

可以多次指定 --test-filter。符合以下任一项的测试: 指定的 glob 模式。

  • --nocapture - 默认情况下输出输出。
  • --list - 不支持列出测试用例。

Go 测试运行程序

请注意以下已知行为变更:

-test.failfast:由于每个测试用例都是在不同的进程中执行的,因此 标志只会影响子测试。

以下标志受到限制,如果以 fuchsia.test.Suite 提供了等效的功能来取代

  • -test.run - 改为使用以下代码:
 fx test --test-filter=<glob_pattern> <test_url>

可以多次指定 --test-filter。符合以下任一项的测试: 指定的 glob 模式。

与运行时无关,包含运行时的测试框架

Fuchsia 旨在实现包容性,例如从某种意义上说, 开发者可以使用自己的语言和运行时创建组件(及其测试) 选择。测试运行程序框架本身在设计上与语言无关, 专门研究特定编程语言或测试的单个测试运行程序 运行时,因此包含各种语言。任何人都可以创建和使用新的 测试运行程序。

创建新的测试运行程序相对简单,并且可以共享 不同运行程序之间的代码。例如,GoogleTest 运行程序和 Rust 运行程序共享与启动 ELF 二进制文件相关的代码,但针对 将命令行参数传递给测试并解析测试结果。

临时存储

如需在测试中使用临时存储,请将以下内容添加到您的组件清单中:

{
    include: [ "//src/sys/test_runners/tmp_storage.shard.cml" ]
}

在运行时,您的测试将拥有 /tmp 的读写权限。 测试开始时,此目录的内容将是空的, 并在测试完成后删除

不指定自定义清单的测试 依赖构建系统生成其组件清单,可以将 以下依赖项:

fuchsia_unittest_package("foo-tests") {
  deps = [
    ":foo_test",
    "//src/sys/test_runners:tmp_storage",
  ]
}

导出自定义文件

如需从测试中导出自定义文件,请使用 custom_artifacts 存储空间 功能。custom_artifacts 的内容会在结论部分复制 。

如需在测试中使用 custom_artifacts,请将以下代码添加到您的组件中 清单:

{
    use: [
        {
            storage: "custom_artifacts",
            rights: [ "rw*" ],
            path: "/custom_artifacts",
        },
    ],
}

在运行时,您的测试将拥有 /custom_artifacts 的读写权限。 测试开始时,此目录的内容将是空的, 并在测试完成后删除

请参阅自定义工件测试示例。如需运行该测试,请将 //examples/tests/rust:tests 添加到您的 build,然后运行以下命令:

fx test --ffx-output-directory <output-dir> custom_artifact_user

测试结束后,<output-dir> 将包含 artifact.txt 文件 测试生成的输出。

Hermeticity

在软件测试中,封闭性是指将 测试或测试套件免受外部因素和依赖的影响, 无论业务流程如何变化,都能得出一致、可靠的结果 对周围环境的影响。封闭测试是独立的,不依赖于 这些外部系统或数据可能会发生意外更改,从而导致不稳定或 非确定性测试结果。

封闭并不意味着要防范稳定的平台或路由 功能/API如果 API Surface 对于 系统,那么它们在测试和依赖组件中将会不稳定, 帮助发现由于系统 API 的任何直接更改或直接更改而导致的回归问题 。

能够编写完全封闭的测试是 Fuchsia 的测试 超能力。测试封闭有两种类型:

  • 功能:测试不使用优惠 测试根目录的父级中的任何功能。这些测试不会 有权访问任何可能会影响较大系统的系统功能。由于 封闭测试的这一属性,它们可以并行运行,不会出现稳定性 由于存在串扰或共享状态的问题,它可以提高模型的稳定性和性能, 测试。

  • 软件包:测试不会解析 测试软件包。封闭封装的测试没有任何隐式协定 和平台软件包这提供了一种更新它们的方法,而不会影响 系统软件包,并避免依赖于不兼容的已打包依赖项。

封闭性不适用于可用于 系统的所有组件例如

  • 时钟
  • 内核提供的标识符,例如 koids。
  • 为所有组件提供的框架功能,允许客户端 转变组件管理器状态,虽然这并非绝对封闭, 组件管理员的责任是确保隔离

使用这些 API/功能时,应格外小心这些测试。

除非另有明确说明,否则这些测试默认是封闭的。

用于测试的封闭功能

有一些功能可供所有测试使用,这些功能不违反测试 封闭性:

协议 说明
fuchsia.boot.WriteOnlyLog 写入内核日志
fuchsia.logger.LogSink 写入 syslog
fuchsia.process.Launcher 从测试软件包中启动子进程
fuchsia.diagnostics.ArchiveAccessor 按测试中的组件读取诊断输出

由于这些功能经过精心挑选,因此保留了私密性 不允许测试影响测试之外的系统组件的行为 领域或其他测试。

要使用这些功能,应在测试的 清单文件:

// my_test.cml
{
    use: [
        ...
        {
            protocol: [
              "fuchsia.logger.LogSink"
            ],
        },
    ],
}

测试还提供了一些默认存储功能, 在测试完成执行后销毁。

存储容量 说明 路径
data 隔离的数据存储目录 /data
cache 隔离的缓存存储目录 /cache
tmp 隔离内存中的临时存储目录 /tmp

在测试的清单文件中添加 use 声明以使用这些功能。

// my_test.cml
{
    use: [
        ...
        {
            storage: "data",
            path: "/data",
        },
    ],
}

该框架还为所有用户提供一些功能, 可以供测试组件使用(如果需要)。

封闭组件分辨率

封闭测试组件是在利用封闭结构的领域中启动的, 组件解析器。此解析器禁止解析 测试的软件包中。这是强制实施封闭性的必要步骤, 系统或 关联的软件包服务器来影响测试结果。

如果尝试解析不在测试软件包中的组件,系统将使用 PackageNotFound 错误以及 Syslog 中显示以下消息:

failed to resolve component fuchsia-pkg://fuchsia.com/[package_name]#meta/[component_name]: package [package_name] is not in the set of allowed packages...

您可以通过添加测试所依赖的任何组件来避免此错误 添加到测试软件包 - 如需查看示例,请参阅此 CL 具体操作步骤,或使用子软件包

# BUILD.gn
import("//build/components.gni")


fuchsia_test_package("simple_test") {
  test_components = [ ":simple_test_component" ]
  subpackages = [ "//path/to/subpackage:subpackage" ]
}
 // test.cml
 {
...
    children: [
        {
            name: "child",
            url: "subpackage#meta/subpackaged_component.cm",
        },
    ],
...
}

有关使用子软件包的示例,请参阅此 CL

// my_component_test.cml

{
...

    facets: {
        "fuchsia.test": {
            "deprecated-allowed-packages": [ "non_hermetic_package" ],
        },
    },
...
}

非封闭测试

这些测试可以访问测试领域之外的一些预定义功能。 一种可通过非封闭测试从其测试领域外部访问的功能称为 系统功能

如需使用系统功能,测试必须明确将其自身标记为以 非封闭领域(如下所示)。

# BUILD.gn (in-tree build rule)

fuchsia_test_component("my_test_component") {
  component_name = "my_test"
  manifest = "meta/my_test.cml"
  deps = [ ":my_test_bin" ]

  # This runs the test in "system-tests" non-hermetic realm.
  test_type = "system"
}

与构建规则集成后,可按以下方式执行测试:

fx test <my_test>

或面向树外开发者

ffx test run --realm <realm_moniker> <test_url>

对于以下代码,其中 realm_moniker 应替换为 /core/testing:system-tests: 上面的示例。

test_type 的可能值:

说明
chromium Chromium 测试领域
ctf CTF 测试领域
device 设备测试
drm DRM 测试
starnix Starnix 测试
system_validation 系统验证测试
system 旧版非封闭领域,可访问某些系统功能。
test_arch 测试架构测试
vfs-compliance VFS 合规性测试
vulkan Vulkan 测试

了解如何创建您自己的测试领域。

非封闭的旧版测试领域

这些都是在推出之前创建的旧版测试领域 测试管理器即服务。我们正在处理 移植这些领域如果您的测试依赖于其中一个领域, 明确将其标记为在旧版领域中运行,如下所示。

// my_component_test.cml

{
    include: [
        // Select the appropriate test runner shard here:
        // rust, gtest, go, etc.
        "//src/sys/test_runners/rust/default.shard.cml",

        // This includes the facet which marks the test type as 'starnix'.
        "//src/devices/testing/starnix_test.shard.cml",
    ],
    program: {
        binary: "bin/my_component_test",
    },
    
    use: [
        {
            protocol: [ "fuchsia.vulkan.loader.Loader" ],
        },
    ],
}

分片在清单文件中包含以下分面:

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "//src/starnix/tests/starnix_test_common.shard.cml" ],
    expose: [
        {
            protocol: "fuchsia.test.Suite",
            from: "self",
        },
    ],
}

fuchsia.test.type 的可能值:

说明
hermetic 封闭王国
chromium-system Chromium 系统测试领域
google Google 测试领域

受限日志

默认情况下,如果测试记录的消息严重性为 ERROR 或更高版本。如需了解详情,请参阅此指南

性能

在编写用于启动进程的测试运行程序时,该运行程序需要 提供库加载器实现。

测试运行程序通常会在单独的进程中启动各个测试用例, 实现更大的隔离程度。然而,这可能会使 但性能会下降。为了缓解这个问题,列出的测试运行程序 使用缓存加载器服务,这样可以减少 每启动一个进程就会产生额外的开销

测试角色

测试领域中的组件可能会在测试中扮演各种角色,如下所述:

  • 测试根目录:位于测试组件树顶部的组件。的网址 测试会识别此组件,然后测试管理器会调用 fuchsia.test.Suite 由此组件公开给 进行测试。
  • 测试驱动程序:实际运行测试并实现 (直接或通过测试运行程序fuchsia.test.Suite 协议。请注意,测试 drive 和 test root 可以是(但不一定是)相同的组件: 测试驱动程序可以是测试根的子组件,该子组件会重新公开其 fuchsia.test.Suite
  • 功能提供程序:一种组件,提供 都会锻炼该组件有可能提供“虚假” 功能测试功能实现,实现 与生产环境中使用的相同
  • 被测组件:执行某些被测行为的组件。 这个组件可能与生产环境中的组件相同 专门用于旨在对生产行为进行建模的测试。

问题排查

本部分包含您在开发测试组件时可能会遇到的常见问题 测试运行程序框架如果您的某个测试组件无法运行,您可能会看到 从 fx test 返回如下错误:

Test suite encountered error trying to run tests: getting test cases
Caused by:
    The test protocol was closed. This may mean `fuchsia.test.Suite` was not configured correctly.

如需解决此问题,请探索以下选项:

测试使用了错误的测试运行程序

如果您在测试枚举期间遇到此错误,那么您可能是 使用了错误的测试运行程序

例如:您的 Rust 测试文件可能会运行 测试时,无需使用 Rust 测试框架(即,这是一个简单的 Rust 二进制文件, 自己的 main 函数)。在这种情况下,请将测试清单文件更改为 elf_test_runner 的要求。

详细了解内置的测试运行程序

测试未能向测试管理器公开 fuchsia.test.Suite

当测试根目录无法从fuchsia.test.Suite test root:简单的解决方法是添加 expose 声明:

// test_root.cml
expose: [
    ...
    {
        protocol: "fuchsia.test.Suite",
        from: "self",  // If a child component is the test driver, put `from: "#driver"`
    },
],

测试驱动程序未能向根目录公开 fuchsia.test.Suite

如果 fuchsia.test.Suite,您的测试可能会失败,并显示类似如下的错误 未正确公开协议:

ERROR: Failed to route protocol `/svc/fuchsia.test.Suite` from component
`/test_manager/...`: An `expose from #driver` declaration was found at `/test_manager/...`
for `/svc/fuchsia.test.Suite`, but no matching `expose` declaration was found in the child

如果“测试驱动程序”与“测试根目录”是不同的组件,则测试驱动程序 还必须向其父级(测试根)公开 fuchsia.test.Suite

要解决此问题,请确保测试驱动程序组件清单包含 以下 expose 声明:

// test_driver.cml
expose: [
    ...
    {
        protocol: "fuchsia.test.Suite",
        from: "self",
    },
],

测试驱动程序不使用测试运行程序

测试驱动程序必须使用适当的测试运行程序 与编写测试时所用的语言和测试框架相对应。 例如,Rust 测试的驱动程序需要以下声明:

// test_driver.cml
include: [ "//src/sys/test_runners/rust/default.shard.cml" ]

此外,如果测试驱动程序是 test root 的子级,则需要 将其提供给驾驶员:

// test_root.cml
offer: [
    {
        runner: "rust_test_runner",
        to: [ "#driver" ],
    },
],

深入阅读