要编写哪些测试

测试有助于发现代码中的潜在问题,各种类型的测试可提供不同级别的覆盖率。

本文档为开发者(组件作者、驱动程序作者和 API/ABI 维护者)提供用于验证 Fuchsia 软件的基本测试。

下表简要介绍了各种测试,这些测试按需要相应类型的测试进行分类。

源代码 组件 驱动程序 协议
单位 全部 - - -
集成 - 全部 部分 -
兼容性 (CTF) - 部分 部分 全部 (SDK)
规格一致性 - 部分 全部 部分
平台预期 - 部分 部分 部分
系统交互(产品) - 部分 部分 部分

以下各部分按照以下标准对测试类型进行分类:

  • 需要什么(哪些源代码、软件包或组件需要这种测试)
  • 测试内容
  • 它的主要优势是什么
  • 在何处查看覆盖率(如何确定此测试是否正常运行并衡量其影响)
  • 如何实现(开发者需要采取哪些措施才能覆盖此覆盖率)
  • 此类测试由谁编写(谁负责提供此覆盖率)
  • 源代码存储在哪里(在哪里放置测试)
  • Fuchsia 何时运行此类测试(在发布流水线中,此类测试运行)

单元测试

  • 需要的是:所有源代码
  • 测试内容:单个软件单元的代码协定
  • 它的主要优势:可实现更有效的重构、优化和开发。
  • 在何处查看覆盖率:https://analysis.chromium.org/coverage/p/fuchsia
  • 实现方式:针对所选语言使用测试框架
  • 此类测试的编写者:所有组件/驱动程序所有者
  • 源代码存储在哪里:在被测试的代码旁边
  • Fuchsia 何时运行此类测试:提交队列和持续集成

所有代码都应包含在能够验证功能的最小测试中。我们使用的语言中的许多函数和类都可以通过集中的小型单元测试直接测试其协定。

单元测试是一个易于理解的领域,在大多数情况下,Fuchsia 开发者可以针对所选语言使用任何测试框架,但有一些显著差异:

  • 在设备上运行的测试(设备测试)以 Fuchsia 软件包的形式分发,并在测试管理器控制的环境中运行。默认情况下,这些测试是完全封闭的,并与系统的其余部分隔离开来,因此任何其他测试都无法看到测试输出的文件。
  • 设备测试可能会公开通过 ffx test 流式传输到主机设备的自定义工件
  • 为生成覆盖率信息而编译的设备测试会自动从设备流式传输数据。ffx coverage 等工具有助于自动处理数据。对于树内测试,Gerrit 和 Fuchsia Coverage 信息中心会显示覆盖率信息,而 OOT 测试执行程序则需要自己的脚本来处理测试的覆盖率输出。

如需了解如何编写驱动程序单元测试,请参阅驱动程序单元测试快速入门

下图显示了在 Fuchsia 系统中运行的单元测试。 单元测试

集成测试

集成测试会检查一个组件的接口和行为是否与调用该组件的另一个组件协同工作。验证不同的组件作为一个系统协同工作并按预期交互。

针对被测组件验证了以下场景:

  • 它实际上能够启动并响应
  • 它按预期对请求做出响应
  • 它按预期与自己的依赖项交互
  • 如果驱动程序由多个组件组成,请检查这些组件在驱动程序内部是否正常运行

建议您使用 Test Realm Factory 封闭(单独)运行集成测试,但如果需要,也可以采用非封闭方式运行集成测试。

封闭集成测试

  • 需求:所有组件和许多驱动程序
  • 测试内容:运行时行为和协定
  • 它的主要优势:确保组件或驱动程序能够自行启动、初始化以及与依赖项交互
  • 在何处查看覆盖率:https://analysis.chromium.org/coverage/p/fuchsia。 请注意,OOT 用例需要更改 build 选项才能输出覆盖范围信息并对其进行处理。
  • 实现方式测试 Realm 工厂
  • 此类测试的编写者:所有组件作者、许多驱动程序开发者
  • 源代码存储在哪里:在被测试的代码旁边
  • Fuchsia 何时运行此类测试:提交队列和持续集成

单元测试较小且侧重于特定业务逻辑,而集成测试则侧重于测试一组软件模块之间的交互。

集成测试是另一个易于理解的领域,但 Fuchsia 提供的测试功能与其他操作系统上的功能完全不同。具体而言,Fuchsia 的组件框架强制要求明确使用和路由“功能”,而该功能以 Zircon 的基于功能的安全性原则为基础。最终结果是 Fuchsia 测试可以明显封闭且递归对称。这些属性的影响被称为“紫红色的测试超能力”。测试可以相互完全隔离,组件可以任意嵌套。这意味着一个或多个整个 Fuchsia 子系统可以在隔离的测试环境(例如,DriverTestRealm测试界面堆栈)中运行。

下图显示了使用测试大区模式的封闭式集成测试。 集成测试

默认情况下,Fuchsia 上的所有测试都是封闭的,这意味着它们会自动从可证明的封闭性以及任意嵌套依赖项的能力中受益。

封闭式集成测试仅以此基础构建,可以在隔离的测试环境中运行一个组件或驱动程序,并使用 FIDL 协议与其进行交互。这些测试涵盖组件/驱动程序的以下场景:

  1. 它实际上能够启动并响应
  2. 它按预期对请求做出响应
  3. 它按预期与自己的依赖项交互

推荐使用 Test Realm Factory (TRF) 编写封闭式集成测试,该模式可让测试做好准备,以便在下述不同类型的测试中重复使用。TRF 测试分为三个部分:

  1. 测试领域,被测组件及其所有依赖项的独立实例。
  2. 测试套件,用于与测试领域交互并对其状态做出断言。
  3. 每个测试套件独有的 RealmFactory 协议,用于构建测试领域。

测试套件中的每个测试用例均调用 RealmFactory 上的方法以创建测试领域,通过其公开的功能与该领域进行交互,并对其收到的响应进行断言。TRF 文档介绍了如何使用 testgen 工具自动创建上述框架并在其中填写详细信息。

使用 TRF 的封闭集成测试为以下多种测试奠定了基础。

所有组件和许多驱动程序都需要进行集成测试,这些测试应使用 TRF。

非封闭集成测试

  • 需求:某些组件和驱动程序。特别是那些具有难以模拟或隔离的依赖项的依赖项(例如 Vulkan)。
  • 测试内容:系统行为和协定
  • 它的主要优势:确保组件或驱动程序能够启动、自行初始化并与依赖项交互,即使其中一些依赖项是系统范围且非封闭的也是如此。
  • 在何处查看覆盖率:https://analysis.chromium.org/coverage/p/fuchsia。 请注意,OOT 用例需要更改 build 选项才能输出覆盖范围信息并对其进行处理。
  • 实现方式测试 Realm 工厂
  • 此类测试的编写者:某些组件和驱动程序作者
  • 源代码存储在哪里:在被测试的代码旁边
  • Fuchsia 何时运行此类测试:提交队列和持续集成

虽然封闭式集成测试是我们应该努力做到的,但某些测试很难以封闭方式编写。这通常是因为,如果依赖项尚未以封闭方式封装,就很难进行这些测试。例如,我们还没有针对 Vulkan 的高保真模拟,因此允许某些测试访问系统范围的 Vulkan 功能。

下图显示了与外部系统、组件或驱动程序交互的非封闭集成测试。 集成测试

访问系统功能的测试称为非封闭集成测试。虽然从技术上讲,它们并不封闭,但仍然应尝试尽可能地隔离:

  1. 仅依赖运行测试所需的功能。
  2. 遵循测试领域工厂样式,尽管功能具有来自系统而非隔离的功能。
  3. 更倾向于依赖尽力在客户端之间实现隔离的功能(例如单独的上下文或会话)。
  4. 测试完成后的清理状态。

非封闭集成测试必须在已路由所需功能的组件拓扑中运行。如需查看现有位置列表以及添加新位置的说明,请点击此处

非封闭集成测试应谨慎使用,并应用于没有封闭解决方案可行的情况。最好将它们用作测试的权宜之计,否则,如果使用适当的模拟或隔离功能,测试会变得封闭。如果测试中有理性地想要在全球范围内声明给定系统的行为,则不应将其用于测试(请改为参阅设备端系统验证主机驱动的系统交互测试)。

兼容性测试 (CTF)

  • 需要执行的操作:在 SDK 中公开的协议,但也适用于客户端库和工具。
  • 测试内容:平台 ABI/API 行为一致性和兼容性
  • 它的主要优势:确保平台协议的行为不会以不兼容的方式发生意外更改。对于平台稳定性尤为重要。
  • 在何处查看覆盖率:CTF 覆盖率信息中心。
  • 实现方式:编写测试领域工厂 (TRF) 集成测试,启用 CTF 模式
  • 此类测试的编写者:SDK 协议实现的全部所有者
  • 源代码存储在哪里:fuchsia.git
  • Fuchsia 何时运行此类测试:提交队列和持续集成

兼容性测试旨在提前发出警告,指出由于平台变更可能会导致下游破坏,因此确保平台 ABI 保持稳定尤为重要。一般来说,SDK 提供的每个 FIDL 协议的稳定 API 都应该对其 API 级别进行兼容性测试。

这些测试用于验证相应协议的客户端(采用稳定的 API 级别且使用 SDK 构建)能否收到与其预期兼容的平台。

FIDL 协议在既定的接口以及展现的行为方面都随着时间的推移而不断发展。当某些协议的输出更改方式与之前的预期不同时,就会出现一种常见的错误。

仅使用集成测试就很难识别这些错误,因为测试通常与要测试的实现同时更新。此外,我们还需要在 SDK 中公开的 API 修订版本之间保持一定的兼容性。

借助适用于 Fuchsia (CTF) 的兼容性测试,您可以根据不同的兼容性测试预期组合对不同版本的组件实现进行测试。TRF 模式与 CTF 完全集成,可通过配置更改(无需更改源代码)指定 TRF 测试进行兼容性测试。

该机制如下:

  • 对于 Fuchsia 的每个里程碑版本 (F#),请保留该里程碑版本存在的测试套件。
  • 对于每项平台变更,针对每个里程碑版本,针对 RealmFactory 运行保留的每个测试套件。

最终结果是,在未来的修改中,对公开协议行为的旧预期将保持不变。如果不提供此覆盖范围,就意味着对 SDK 协议的行为或接口的细微更改会导致下游破坏,这尤其难以根本原因。

下图显示了使用测试领域工厂 (TRF) 模式和冻结组件模拟的兼容性测试。 兼容性测试

为 TRF 测试启用 CTF 模式是一个简单的配置选项,将现有集成测试转换为 TRF 也很简单(示例)。实现 SDK 协议的组件/驱动程序的作者应优先将其测试转换为 TRF,并启用 CTF 模式,以帮助稳定 Fuchsia 平台并节省持续的大规模更改开销。

在合作伙伴或公共 SDK 中公开协议的所有组件都应进行 CTF 测试。

规格一致性测试

  • 需要执行的操作:树外 (OOT) 驱动程序、正在从一个实现迁移到另一个实现的组件(某些)框架客户端。
  • 测试内容:协议或库的不同实现之间的一致性,以确保它们符合规范。
  • 它的主要优势:确保同一接口的不同实现表现出兼容的行为。对于可能有多个同一接口的实现者的驱动程序,这尤其有用。
  • 在何处查看覆盖率:TODO
  • 实现方式:使用现有 TRF 集成测试的某些部分创建新的 TRF 测试。或者,在 Lacewing 中编写此测试,与整个系统进行交互。
  • 此类测试的编写者:合同/协议所有者针对要求定义测试,下游用户会重复使用或组合测试部分以确保其代码满足要求。
  • 源代码存储在哪里:fuchsia.git,或者通过 SDK 在独立代码库中构建
  • Fuchsia 何时运行此类测试:提交队列、树外持续集成。

Fuchsia 平台通常会定义必须通过一个或多个实现执行的协定,其中某些实现可以在 fuchsia.git 代码库之外进行定义:

  1. Fuchsia SDK 定义了必须由 Fuchsia 源代码树(树外)外部的驱动程序实现的驱动程序 API。驱动程序必须生成与平台对其预期相符的结果。
  2. 组件重写包含一个现有代码库和一个正在进行中的代码库,这两个代码库必须保持一致。例如,如果要重写文件系统存储堆栈,我们希望确保写入和读取文件生成的结果与原始实现一致。
  3. Inspect 和 logs 等客户端库采用多种语言实现,其实现包括编写二进制格式。重要的是,操作 LOG("Hello, world") 会生成二进制兼容输出,无论生成它的库是什么。

请务必注意,实现符合规范,规范一致性测试用于验证是否符合规范。

用于规范一致性测试的封闭方法

我们通过执行驱动程序并检查其是否符合预期行为来测试驱动程序一致性。对于控制设备的驱动程序,这需要在硬件上运行(推荐方法)。此测试由驱动程序开发者在他们的桌面(在硬件上)和 CI/CQ 中使用 SDK 运行

规范一致性测试可以在 TRF 测试的基础上进行构建,使其结构与兼容性测试完全相同。在这种情况下,主要区别在于如何使用 TRF 测试的不同部分。

规范一致性测试的推荐模式是定义一个 RealmFactory(包含被测实现)、一个测试套件(验证该规范的实现情况),在定义协定的位置(例如,对于 SDK 协议使用 fuchsia.git),以及用于驱动测试的 FIDL 协议(负责对一组被测组件进行实例化并与其交互)。测试套件和 FIDL 协议会分发给实现者(例如,通过 SDK)。实现合约的开发者可以使用分布式测试套件,并实现自己的 RealmFactory,将其实现封装在 FIDL 协议之后。这意味着,定义协定的同一组测试会应用于每个实现。

下图显示了以封闭方式运行规范一致性测试的方法。 规范一致性测试 (Hermetic)

使用非封闭方法进行规范一致性测试

测试规范一致性的非封闭方法是使用 Lacewing(依赖于硬件的实现)在工作系统上运行测试。此测试由开发者在 CI/CQ 中针对产品实现运行。

下图显示了以非封闭方式运行规范一致性测试的方法。 规范一致性测试(非封闭)

主机驱动型方法,用于规范一致性测试

或者,也可以使用 Lacewing 编写规范一致性测试,并作为主机驱动的系统交互测试运行。当协议的实现者是驱动程序或以其他方式依赖于特定硬件时,这特别有用。这对于断言包含驱动程序的产品映像符合规范并且已经过适当组装以支持与硬件交互尤为有用。

下图显示了以主机驱动的方式运行规范一致性测试的方法。 规范一致性测试(由主机驱动)

更具体地说,我们可以按如下方式求解上述示例:

  1. 驱动程序:在 fuchsia.git 中定义驱动程序 FIDL。使用关联的测试套件和 FIDL 协议创建 TRF 测试,并在 SDK 中提供这些测试。在树外驱动程序代码库中,实现驱动程序,创建一个封装驱动程序并实现测试 FIDL 的 RealmFactory。针对该 RealmFactory 运行分布式测试套件。
  2. Drivers (Lacewing):创建与来自主机的司机 FIDL 协议进行交互的 Lacewing 测试。将此测试作为可在树外驱动程序代码库中或产品组装期间运行的二进制工件提供。
  3. 组件重写:为重写创建一个新的代码库,使用现有实现中的测试套件创建一个实现测试 FIDL 的框架 TRF 测试(遵循最佳实践,返回非失败的 UNSUPPORTED 值)。以增量方式重写组件,随着测试用例开始通过将失败情况设置为阻塞,则这些情况会变为阻塞。在所有测试用例通过后,重写完成,删除旧实现。
  4. 多种语言的客户端库:将库中可能的操作定义为测试 FIDL。为该库的一个实现创建 TRF 测试,然后将该实现中的测试套件用于所有其他实现。它们将保持合规。

对于预计会多次实现的接口,应针对集成商提供规范一致性测试,以便在其基础上进行构建。

平台预期测试

  • 需求:许多平台组件和驱动程序。例如,驱动程序应确保它们与组装系统上的实际硬件进行交互。
  • 测试内容:组装产品映像上组件/驱动程序的行为
  • 它的主要优势:确保平台组件/驱动程序在安装在实际产品中时的行为符合预期。有助于发现功能路由以及与真实硬件交互方面的问题。
  • 在何处查看覆盖率:TODO
  • 实现方式:编写测试领域工厂 (TRF) 集成测试,创建执行领域,启用系统验证模式 (TODO)。进行对规范断言的测试,并在流水线的每个阶段运行这些测试(强烈建议重复使用)。利用平台预期测试套件 (PETS)。
  • 此类测试的编写者:平台组件和驱动程序的所有者
  • 源代码存储在哪里:fuchsia.git,在 SDK 中传送到树外代码库
  • Fuchsia 何时运行此类测试:提交队列、树外持续集成。

下图显示了平台预期测试,这些测试随 SDK 一起提供。

平台预期测试

封闭的集成测试可确保组件在隔离环境中正常运行,但无法验证包含该组件的组装系统映像是否正常运行。系统验证测试是一种特殊的非封闭集成测试,可确保真实组件的行为符合预期,但会受到一些限制。

平台预期测试通常基于由 RealmFactory 和测试套件的封闭 TRF 测试。系统验证测试使用的是提供对真实系统功能的访问权限的替代组件,而不是 RealmFactory(用于实例化被测独立组件)。

例如,如果您要测试 fuchsia.example.Echo,封闭式 TRF 测试将提供一个会公开 fuchsia.example.test.EchoHarness 的 RealmFactory,您可以通过它 CreateRealm() 获取隔离的 fuchsia.example.Echo 连接。系统验证测试的替代组件也实现 CreateRealm(),但提供来自系统本身的真实 fuchsia.example.Echo 连接。

此模式允许您在封闭和非封闭情况下使用完全相同的测试代码,并由 UNSUPPORTED 返回值处理不兼容性。

为说明其工作原理,不妨考虑使用包含 SetUpDependency()CreateRealm() 方法的自动化测试框架进行系统验证测试。如果在非封闭设置下运行时无法设置依赖项,该方法仅返回 UNSUPPORTED,系统会跳过依赖于该依赖项的测试。考虑使用 read_any_data()read_specific_data() 测试用例,它们跳过调用 SetUpDependency() 并分别进行调用。前一种情况确保能够以正确的格式读取任何数据(无论是封闭方式还是非封闭方式),而后一种情况确保返回特定数据(仅以封闭方式)。

为帮助 OOT 系统集成商,我们可能会在 SDK 中提供系统验证测试套件,以便针对组合后的产品映像 OOT 运行。这是用于验证 OOT 驱动程序行为的主要机制。

平台组件和驱动程序应具有系统验证测试。Fuchsia SDK 应为预计在单独的代码库中实现的每个驱动程序提供一个验证测试套件。

系统交互(产品)测试

  • 需要什么:一些组件和驱动程序;各个用户体验历程(例如,验证用户轻触屏幕时的响应)。
  • 测试内容:系统服务和个别用户体验历程中的行为回归问题。
  • 主要优势:可完全控制一个或多个 Fuchsia 设备和非 Fuchsia 外围设备。涵盖需要多台设备、需要重新启动设备或模拟用户与设备互动的情况。
  • 在何处查看覆盖率:TODO
  • 实现方式:使用 Mobly/Lacewing 编写 Python 测试
  • 此类测试的编写者:组件/驱动程序所有者;产品所有者
  • 源代码存储在哪里:fuchsia.git 或 OOT
  • Fuchsia 何时运行此类测试:提交队列、OOT 持续集成

借助 Fuchsia,您可以在各种情况下无法进行在其他系统上写入的封闭测试,但在某些情况下,整个设备的物理控制都无法替代。主机系统不是在受测设备上运行这些测试,而是负责控制一个或多个设备以执行端到端代码路径。

主机驱动的系统交互测试能够使用 SDK 工具完全控制 Fuchsia 设备,并直接连接到设备上的服务。它们是使用 Lacewing 框架(基于 Mobly 构建)编写的。

使用 Lacewing 的测试可以任意连接到目标系统上的服务。有些测试是针对特定驱动程序或子系统编写的(例如,实时时钟是否可以在重新启动后节省时间?),还有一些测试旨在涵盖需要设备级配置的用户历程(例如,已登录的用户是否可以打开网络浏览器并导航到网页?),还有一些测试旨在控制大量的 Fuchsia 设备和非 Fuchsia 设备兼容。

采用 Lacewing 框架时,通过“affordance”来处理与设备的交互,这些“affordance”提供可与特定设备子系统进行交互的可演进接口(例如蓝牙功能、WLAN 功能等)。

示意图显示了使用 Lacewing 运行系统交互测试的方法。 系统交互测试

与大多数端到端 (E2E) 测试一样,这种测试的成本可能很高,原因如下:

  • 测试通常需要在特定的真实硬件设置上运行。
  • E2E 测试性能较低,这会增加 CI/CQ 延迟时间和负载。
  • E2E 测试不是隔离的,如果未适当管理状态,可能会增加其不稳定性。

出于这些原因,E2E 测试应谨慎进行,但 E2E 测试往往是涵盖最难测试差距之一的最后一道防线:确保与系统互动的真实用户能够看到预期输出。

某些系统组件和驱动程序需要这种测试,但使用 Lacewing 的主要好处是可以涵盖隔离的设备端测试无法涵盖的实际设备交互。在系统验证和 Lacewing 之间选择通常是一项判断力,但在完整的测试策略中,可以留出同时进行这两种测试的空间。测试作者应设法以最低的维护费用获得所需的覆盖范围。