树外系统测试支持

  • 项目负责人:shayba@google.com、ananthak@google.com、fsamuel@google.com
  • 项目合作伙伴:borthakur@google.com、jasoncampbell@google.com、lite@google.com、ppi@google.com
  • 地区:测试

问题陈述

Fuchsia 平台和产品开发者对树外系统测试的需求目前要么未得到满足,要么是通过错误的工具得到满足,导致测试覆盖率不足或测试质量低下。

基于 Fuchsia 的产品开发(在 Fuchsia 源代码树之外进行,即树外 [OOT])严重依赖于非沙盒化系统测试,这些测试在平台严格定义的合同之外对 Fuchsia 平台进行测试。之所以这样做,是因为平台组件大多不提供支持的方式来执行必要的插桩,以满足产品测试的需求。测试作者已经能够找到绕过这些限制的方法,但这些解决方案会导致测试质量低下。

如果对 Fuchsia 平台的开发流程进行能够规避平台合约的 OOT 测试,则会威胁到 Fuchsia 的可更新性

什么是系统测试?

系统测试是对完整的组装系统进行的测试,目的是验证系统是否满足特定要求。在实用测试金字塔中,系统测试可补充单元测试和集成测试,以填补只能通过观察完整的受测系统来解决的测试空白。

系统测试有时也称为:

  • 端到端(或 e2e)测试,尤其是当系统测试的范围超出特定目标设备,还包括(例如)通过网络接口连接的远程服务器或控制宿主机时。

  • 关键用户历程 (CUJ) 测试,尤其是当测试以模拟和自动化的用户输入和输出(例如通过注入按钮按压等输入事件,并将界面状态摘要或屏幕截图与预期测试结果进行比较)来表示时。

此外,还可以进一步检测这些测试,以产生更多价值,具体如下:

  • 性能测试,其中除了执行产品 CUJ 之外,测试框架还会收集性能信息,例如时间、轨迹、FPS 统计信息等。

  • 耐久性测试,其中在紧密循环中执行相同的 CUJ,并监控系统是否存在资源泄漏(RAM、句柄等)或崩溃等压力迹象。

还有一类系统测试,用于测试平台功能,并在树内开发。这些内容不在本文档的讨论范围内。

Fuchsia 目前面临的系统测试挑战

非封闭旧版 (v1) 组件测试

Fuchsia 的组件框架提供广泛的测试支持,测试环境与系统的其余部分之间具有高度隔离。系统仍支持旧版 (CFv1) 测试,以测试尚未迁移到 CFv2 的旧版组件。虽然大多数生产组件仍为 CFv1,但大多数测试(超过 70%)都使用 CFv2 测试框架,因为该框架更可靠,并为开发者提供了一些新功能。

出于历史原因,v1 测试运行时在许多方面都不是密封的,例如允许访问某些真实系统服务。因此,许多 CFv1 测试实际上会无意中充当系统测试。这些测试存在多种问题,包括:

  • 测试失败可能难以排查,因为测试的完整范围非常广泛或未严格定义。

  • 测试可能会受到外部状态的影响,或者将外部状态作为副作用。因此,这些测试可能会与其他此类系统测试发生串扰,从而导致无法重现的故障(“flake”)或在不稳定条件下重现的故障,例如通过以不同顺序重新运行相同的测试。

  • 假设其他系统组件的实现细节超出其声明的合同的测试。

  • 组件测试框架旨在用于编写隔离的密封单元测试和集成测试。在此类测试中,任何失败原因都只能来自测试领域内。由于这种隔离预期,因此还预期任何两项测试都可以并发运行或以任意顺序运行,而不会影响其结果。使用已弃用的 CFv1 功能来破坏测试沙盒并编写系统测试会打破这些保证,并造成难以排查的问题。许多此类测试的作者并未意识到,这些测试实际上是系统测试。

违反 Fuchsia 系统接口的 OOT CUJ 测试

对于 OOT 软件,与 Fuchsia 平台交互的预期方式和支持方式是通过 Fuchsia 系统接口 (FSI)。不过,目前 OOT 开发者可以使用一些工具来绕过此接口并违反平台产品合同。

Fuchsia 脚本层 (SL4F) 是一种系统自动化框架,旨在编写全面的系统测试。

SL4F 的灵感源自 Android 脚本层 (SL4A)。它最初是为树内平台系统测试设计的。特别是,SL4F 非常适合移植 Android 通讯测试套件 (ACTS) 测试等内容,这些测试使用相同的底层 JSON-RPC/HTTPS 协议来驱动目标设备。这种安排对于 Fuchsia 连接测试非常有用。

作为系统自动化框架,SL4F 还可用于测试 CUJ。例如,SL4F 为平台 CUJ 测试提供支持。

不过,SL4F 并非专为 OOT 测试而设计。与 SL4F 的互动是通过 FSI 之外的协议完成的,这些协议无法提供与 FIDL 相同的演变机制。通过引入新的 facade 可以扩展 SL4F 的自动化功能,但所有 facade 都必须在树内开发和构建。因此,在测试定义和开发 OOT 的产品的 CUJ 时,使用 SL4F 会导致下面列出的一些问题。

另一种允许进行过度侵入性测试的常见机制是使用 SSH 来获取远程 shell 并在主机和目标之间复制文件。请勿将此与使用 SSH 作为隧道协议混淆,后者可作为 Overnet 的传输协议。

目前的 Fuchsia 工程 build 包含一个 SSH 守护程序,该守护程序以不受沙盒限制的方式访问全局命名空间,并向客户端提供 dash shell。同一守护程序还允许 SCP 功能对全局命名空间(例如全局可变存储空间)进行类似程度的读/写访问。但实际上,这往往会成为绕过 FSI 的一种方式,让测试作者能够通过观察或更改平台组件在可变存储空间中的状态来违反平台组件的支持接口,从而依赖于 Fuchsia 基本软件包的名称等平台实现细节。

SSH 和 SCP 访问权限已挂钩,以便测试作者轻松使用 Dart 中的 SL4F 客户端库。

脆弱的测试模式

SL4F 为测试作者提供了多种方法来操纵和观察系统状态。其中一些机制会绕过平台产品合同和必要的抽象层。编写 SL4F 测试时,不一定非要使用这些机制,而且并非所有测试都必须使用这些机制。但这些欢迎性存在会使许多反模式进入我们的 SL4F 测试清单。

值得注意的是,这些模式是出于必要性而开发的,因为该平台未能提供可靠的替代方案来满足测试需求。我们在此列出这些模式,并非为了批评平台开发者或测试作者,而是为了了解和归类我们现在必须偿还的技术债务。

通过非合约观察状态

被测代码可能会向系统日志或通过 Inspect 发出有关其状态的信息。这些都是有用的工具,可用于将实例的诊断信息收集到快照中。不过,它们并非旨在成为合同。FIDL 在 Fuchsia 上用于定义强类型合约,这些合约可以稳定且具有演变机制,例如对线路格式和版本控制的二进制兼容更改,从而允许非协调的客户端和服务器交换 FIDL 消息。FIDL 经过精心设计,可用于此目的,而自由文本日志和 Inspect 则不然。

以下是一些值得注意的具体示例

测试中的日志:有些长期测试会使用严重程度为“错误”或更高级别的带注释日志,以确定产品在测试期间是否承受了意外压力。遗憾的是,在测试执行期间发出的“错误”消息从测试作者的角度来看通常是良性的。因此,持久性测试的作者不可避免地需要维护一个已记录的错误消息的许可名单。

在测试中检查:某些测试驱动程序会从其驱动的组件读取 Inspect 信息,以观察这些组件的状态。由于 Inspect 是类型化数据,并且可以作为单个连贯的快照获取,因此对于组件的作者来说,它是一种有用的工具,可用于诊断组件的当前状态。不过,当它用作平台组件和产品测试之间的合约时,会形成脆弱的 ABI,并经常导致中断。由于这些中断可能发生在底层平台更改落地数周后(当尝试进行 SDK 滚动时),因此很难进行问题排查。

通过非合约操纵状态

SL4F 测试对宿主具有很大的控制权,包括能够在非沙盒化 shell(即通过全局命名空间)中执行任意 SSH 命令,以及对全局不可变和可变存储空间具有完全的读/写访问权限。以下是一些关键示例:

终止和重启进程:这通常用于测试中的设置和拆解例程。此 intent 为正值,表示测试想要清除任何之前的状态并重新开始。不过,被测的平台组件不一定经过多次或以不同顺序重启的鲁棒性测试,这通常会导致不稳定的行为。

此方法的另一个问题是,通过让 OOT 测试终止平台进程,进程名称(不属于平台合同的一部分)会成为合同。此类对预期接口和合约的违规行为会增加平台重构的难度,并使 OOT 测试变得更加脆弱。

操纵可变存储:这种明显的沙盒违规行为通常用于在设置步骤中注入用户凭据,以进行某些 CUJ 测试。状态注入的时间早于受测代码读取该状态的时间,而不是操作用于凭据注入的预期接口。如果时间不正确,测试会失败。如果清理不成功,后续测试可能会失败,因为它们会受到测试串扰的影响。

全局可变文件系统访问权限的另一个使用情形是使用全局 /tmp 存储目录作为测试结果、制品和诊断信息(例如在测试期间收集的性能轨迹)的临时存储区。同样,测试可能会因无法清理状态、通过串扰相互影响或通过其他方式注入虚假或不稳定的行为而失败。

不同组件实例的隔离存储目录在同一分区上进行管理,因为为不同组件创建单独的分区既昂贵又不灵活。通过在共享分区上创建特定的目录布局来实现隔离。目录布局反映了平台实现细节,例如组件拓扑或 Component Manager 如何将该拓扑转换为文件系统部分。这些再次说明了平台实现细节,这些细节会随着时间的推移而发生变化,并且不应向 OOT 测试公开。

成效

在同一测试中混用开放式测试和封闭式测试会产生低质量的测试,即设置了不相关预期,并以被测软件不支持的方式编排和观察状态的测试。

目前的情况源于长期以来对系统测试工具作为 Fuchsia 平台产品的忽视。尽管如此,我们发现自己面临着许多不属于现有类别且不符合测试最佳实践的特殊测试。

更糟糕的是,这些测试所依赖的平台实现细节是以无法实现演进的方式表达的。例如,许多 FSI 都是根据 FIDL 精确定义的。FIDL 旨在让平台开发者能够做出 ABI 兼容的变更,或检测现有 ABI 是否因特定变更而遭到破坏。FIDL 为开发者提供了多种更改类型和协议的方式,而不会破坏 ABI,或者以受控方式引入破坏性变更:稳定的协议方法序号、灵活的表、版本控制等。

与此相反,进程名称也可以作为一种契约,例如用于让测试终止进程。由平台组件实现的进程的名称绝不会成为 FSI 的一部分,部分原因是它没有演进的可供性 - 任何更改都是破坏性更改,没有版本控制的可供性,并且很少能同时运行旧进程和新进程,以实现临时兼容性窗口。全局文件系统路径、内部文件格式、自由文本日志和其他此类非合约也适用此规则。

缺少 OOT 支持

SL4F 客户端可以通过与 SL4F 守护程序对话来自动执行系统操作,该守护程序包含在通过 Fuchsia SDK 版本滚动分发给 OOT 测试人员的平台映像中。该守护程序可以执行不同的任务来编排和检查系统状态,这些任务被分组为“外观”。系统这方面通常运行良好,因为这是一个稳定的合约,并且有合理的手段可以随着时间的推移对其进行改进。

不过,开发 facade 只能在树内完成,这意味着目前无法通过 OOT 扩展系统自动化功能。这并不是 SL4F 的令人惊讶的属性,它只是未设计为供 OOT 客户端使用。

单独的堆栈

SL4F 提供了配置、设备发现、主机-目标网络传输协议、一些可扩展性机制以及向 SDK 客户交付客户端库和工具的方法。

ffx 也以更好的方式解决了同样的问题。在开发 ffx 插件时,如果主机端插件需要目标侧协作组件来控制目标,则可以开发 FIDL 代理。然后,主机和目标之间的通信将通过 FIDL 定义,与通过 HTTP 的 JSON-RPC 不同,FIDL 可以是 FSI 的一部分,并且可以作为与平台其余部分和 SDK 的合约不断发展。这在一定程度上是因为,与仅在代码中存在的无类型 JSON 合约相比,保持与 FIDL 的 ABI 兼容性更容易。

相应地,ffx 堆栈可以更好地了解 Fuchsia 组件,例如知道何时以及如何启动测试所需的组件。它还解决了 SL4F 堆栈甚至无法处理的问题,例如主机-目标身份验证。这样一来,便可在 userdebug build 类型中使用 ffx,而 SL4F 仅允许在 eng build 中使用。

其他自定义测试框架

除了上述解决方案之外,还有其他 OOT 测试解决方案。这些测试涵盖了完整的测试范围,但它们主要以系统测试的形式进行编排,因为它们不使用平台自身的隔离机制进行测试。

部分 OOT 合作伙伴会部分且不一致地共享同一组测试解决方案和工具。这导致技术债不断增加。

解决方案陈述

Fuchsia 平台团队将在树内和树外创建强大的系统测试解决方案。平台团队将与产品团队合作,了解产品测试需求、满足这些需求,并协助产品团队完成任何迁移。

新解决方案将利用组件框架ffx 插件的沙盒功能来:

  1. 能够编写出色的 OOT 测试,包括出色的系统测试。
  2. 使编写违反 Fuchsia 平台严格定义和支持的协定的 OOT 测试变得更加困难(几乎不可能)。

具体而言:

在适用的情况下,推广组件化单元测试和集成测试

如果可以将 OOT 测试重新实现为在测试 realm 中运行的组件,以生成封闭的单元测试或集成测试,我们将重新实现这些测试。为了实现并推广更健康的测试金字塔,我们将提供与树内组件测试同等水平的 OOT 组件测试支持。

重新实现了所有非封闭的 CFv1 测试

所有通过访问真实系统服务来违反密封性的现有旧版 CFv1 测试都将以其他方式重新实现,以满足相同或更高的测试要求。这些测试是以密封的 CFv2 组件测试、新的系统测试还是其他形式重新实现,取决于被测代码的所有者。

如果平台测试支持缺失或不足(例如 CFv1 组件所有者无法迁移到 CFv2),相关平台团队将与测试所有者合作,创建必要的平台解决方案。

为产品所有者创建新的系统测试平台解决方案

Fuchsia 平台团队将开发一种新解决方案,以编写满足以下条件的系统测试:产品所有者 <0x0

  1. 系统测试可以 OOT 方式开发和执行。

  2. 调用这些测试并收集其结果的方式只有一种,这样可确保无论是在本地开发者工作流中还是在 CI/CQ 自动化中执行测试,无论使用何种自动化解决方案,都能遵循相同的工作流并获得相同的一致结果。

  3. 平台中不属于预期合同一部分的详细信息(例如 Fuchsia 系统接口中定义的内容)不会向 OOT 系统测试开发者公开。为了实现所需的沙盒化级别,测试和测试框架(如果尚未迁移)将迁移到 CFv2。

  4. 系统测试框架与 ffx 共享尽可能多的相关堆栈。这包括配置、主机工具和客户端库分发机制、版本控制、配置、目标设备发现、主机-目标身份验证、主机-目标传输以及远程控制机制等。

  5. 平台系统组件的所有者可以(并且将会)扩展系统测试框架,以满足产品开发者的测试需求。这样一来,平台开发者将创建可维护的 ABI,并确立对这些可测试性合约的长期所有权。

减少并淘汰旧版解决方案

平台和产品团队将共同努力,淘汰旧解决方案,并在所有地方统一使用新的受认可的系统测试框架。从长远来看,除了存在继续使用旧版测试解决方案的特殊激励措施(例如,为了实现所需的跨平台测试套件兼容性)之外,旧版测试解决方案将不会再使用。

优先级规则

相关团队可自行决定工作的优先级。不过,我们建议您优先处理那些维护成本最高的测试,例如先解决技术债务曲线前端的问题。

无论选择哪些具体任务,在考虑可以采取哪些措施来偿还技术债务时,现代化系统测试方面的工作都将被视为高优先级任务。

依赖项

为了弥合平台可测试性方面的差距,并开发和推出新框架,需要多个平台团队(包括组件框架、测试架构、SDK 工具、EngProd 测试和 EngProd 基础设施)共同努力。

此外,还需要与所有产品合作伙伴团队和多个平台组件所有者合作,帮助发现可测试性差距,并执行向新测试框架和解决方案的迁移。

风险和缓解措施

系统测试不限定于特定组件或软件包,而是限定于整个被测系统。因此,无法严格定义实际测试的代码。将系统测试从一个框架迁移到另一个框架时,可能会丢失一些作为系统测试附带好处而获得的有益测试覆盖率。

某些系统测试的范围非常大,这使得重新实现它们的难度非常高。作为有用的预先处理步骤,缩小或拆分部分测试范围可能很有益。

部分现有系统测试没有专门的长期所有者。此类废弃软件可能难以使用,并且一些迁移工作将不可避免地由不同的人员负责。

为了与现代解决方案(尤其是 OOT)保持一致,需要进行 OOT 和产品端 CFv2 迁移。CFv2 迁移取得了巨大进展,但到目前为止,重点一直放在系统组件上,尚未开始迁移 OOT 或产品端组件。出现未知阻碍因素是合理的。

与往常一样,偿还技术债务会与团队的其他优先事项竞争。需要领导层保持一致,才能有效执行此过渡。

不在范围内

树内系统测试

在源代码树中开发的测试不在本文档的讨论范围内。虽然上述工作可能会带来意外的收获,从而有利于树内系统测试,但树内测试的问题陈述差异很大,因此此处不作讨论。例如,树内测试可以在 Fuchsia 平台接口下运行,而不会影响 Fuchsia 的可更新性原则和目标。

组件测试

对于组件测试(即表示为一组实现单元测试或集成测试的一个或多个组件的测试),请参阅有关树外组件测试支持的路线图文档

特殊可移植性要求

在某些特殊情况下,需要在 Fuchsia 上运行预先存在的测试套件,以展示与另一实现的兼容性或合规性,或针对另一实现进行基准比较。在这种情况下,必须运行未修改的现有测试,否则无法有把握地证明兼容性。这反过来又基本上定义了设备端测试自动化系统的规范。

解决此问题的一种常见方法是将预先存在的测试框架移植到 Fuchsia。例如,Fuchsia LLVM Toolchain 团队和 Fuchsia Rust Toolchain 团队计划分别移植 LLVM 和 Rust 测试框架,以便在 Fuchsia 上运行来自上游的测试。这样做的好处是,可将 Fuchsia 升级到更高级别的工具链支持,以分别支持这些外部项目。

另一种常见解决方案是根据同一规范重新实现测试框架。例如,Fuchsia Connectivity 团队根据 SL4A 的 JSON-RPC/HTTPS 规范实现了 SL4F,以便在 Fuchsia 上运行 ACTS 测试。这有助于将大量有用的测试引入 Fuchsia,并展示连接领域至关重要的兼容性。

只要以正确的理由执行这些解决方案并以严谨的方式使用,它们就能很好地解决这一独特的问题空间。举个反例,如果我们把 LLVM 测试框架移植到 Fuchsia,然后使用该框架编写了新的测试,但这些测试不在演示兼容性或与合作伙伴项目共享测试的范围内,那么就需要提供额外的理由,否则平台团队会不鼓励这样做。