树外系统测试支持

  • 项目负责人: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) 测试,尤其是当测试以模拟和自动的用户输入和输出表示时,例如通过注入按下按钮等输入事件并将界面状态摘要或屏幕截图与预期测试结果进行比较。

此外,这些测试可以进一步进行插桩 (instrument) 处理以生成额外的值,表示如下:

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

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

此外,还有一整类系统测试会执行平台功能并在树内开发。它们不在本文档的讨论范围内

目前 Fuchsia 上的系统测试挑战

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

Fuchsia 的组件框架提供广泛的测试支持,在测试环境与系统的其余部分之间实现了高度隔离。系统仍支持使用旧版 (CFv1) 测试来测试尚未迁移到 CFv2 的旧版组件。虽然大多数正式版组件仍然是 CFv1,但大多数测试 (>70%) 都使用 CFv2 测试框架,因为它更加可靠,并为开发者提供了一些新功能。

由于传统原因,v1 测试运行时在很多方面并不封闭,例如允许访问某些实际系统服务。因此,很多 CFv1 测试实际上会像系统测试一样运行。这些测试遇到了多个问题,包括:

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

  • 测试可能会受到外部状态的影响,或者会留下外部状态作为附带效应。因此,这些测试可能会与其他此类系统测试交叉通信,导致无法重现的故障(“不稳定”)或者在不稳定条件下重现的故障(例如,通过以不同的顺序重新运行相同的测试)。

  • 假定了声明协定以外的其他系统组件实现细节的测试。

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

破坏 Fuchsia 系统接口的 OOT CUJ 测试

对于 OOT 软件,预期和受支持的方式是通过 Fuchsia 系统接口 (FSI) 与 Fuchsia 平台交互。不过,目前有一些工具可供 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 基础软件包的名称)。

为了方便测试作者使用,例如使用 Dart 中的 SL4F 客户端库,连接了 SSH 和 SCP 访问权限。

脆弱测试模式

SL4F 为测试作者提供了许多操纵和观察系统状态的方法。其中一些机制会绕过平台-产品协定和必要的抽象层。在编写 SL4F 测试时,并非绝对必须使用这些机制,而且并非所有测试都一定会使用它们。然而,这些欢迎的存在促使许多反模式加入我们的 SL4F 测试目录。

值得注意的是,这些模式是确有必要开发的,因为平台无法提供可靠的替代方案来满足测试需求。我们在下面列出这些模式,并不是批判平台开发者或测试作者,而是为了了解我们现在必须偿还的技术债务并对其进行分类。

通过非协定观察状态

被测代码可能会将其状态相关信息发送到系统日志,也可以通过 Inspect 将其发出。这些是有用的工具,可用于将诊断信息收集到例如快照中。不过,它们并不是合同形式。FIDL 在 Fuchsia 上使用以定义强类型协定,这些协定保持稳定且具有演化机制,例如对传输格式和版本控制进行二进制兼容的更改,这允许不协调的客户端和服务器交换 FIDL 消息。FIDL 为此非常谨慎,而自由文本日志和 Inspect 则不然。

需要注意的一些具体示例

测试中的日志:一些持久性测试使用严重级别为“error”或更高级别的日志来标识产品在测试期间处于意外压力。遗憾的是,在测试执行期间发出的“错误”消息对于测试作者来说通常是良性的。因此,长时测试作者不可避免地需要维护一个已记录错误消息的许可名单。

在测试中进行检查:一些测试驱动程序会从其驱动的组件中读取 Inspect 信息,以观察这些组件的状态。由于 Inspect 是类型化数据,并且可以作为单个连贯快照获取,因此它是一个非常有用的工具,用于由组件的作者诊断组件的当前状态。但是,当用作平台组件与产品测试之间的协定时,会导致 ABI 脆弱,并且经常会导致破坏。这些破坏很难排查,因为可能在底层平台变更推出几周后,即尝试 SDK 滚动。

通过非协定操纵状态

SL4F 测试可以更好地控制主机,包括能够在未沙盒化 shell(即通过全局命名空间)中执行任意 SSH 命令,以及对全局不可变和可变存储空间的完全读写权限。一些重要示例:

终止和重启进程:这通常用于测试的设置和拆解例程。意图是积极的 - 测试希望清除任何先前的状态并开始刷新。不过,被测平台组件无需经过测试即可稳健性地重启多次或按不同顺序重启,这往往会导致行为不稳定。

这种方法的另一个问题是,通过让 OOT 测试终止平台进程,进程名称(不属于平台协定的一部分)会成为协定。此类违反预期接口和协定的行为会增加平台重构的难度,也会导致 OOT 测试更加脆弱。

操纵可变存储空间:这种明显的沙盒违规行为通常用于注入用户凭据,作为某些 CUJ 测试的设置步骤。系统会在被测代码读取上述状态之前注入状态,而不是运行预期接口以进行凭据注入。如果时间不合适,测试会失败。如果清理不成功,后续测试可能会失败,因为它们面临测试串扰。

全局可变文件系统访问的另一个用例是使用全局 /tmp 存储目录作为测试结果、工件和诊断(例如在测试期间收集的实例性能跟踪记录)的暂存区域。同样,这有机会让测试无法清理状态、无法通过串扰相互影响,或者通过其他方式注入虚假或不稳定的行为。

不同组件实例的隔离存储目录在同一分区上进行管理,因为为不同组件创建单独的分区成本高昂,并且不够灵活。通过在共享分区上创建特定的目录布局来实现隔离。目录布局反映了平台实现细节,例如组件拓扑或组件管理器如何将该拓扑转换为文件系统部分。同样,这些也是平台实现细节,可能会并将来会发生变化,并且不应提供给 OOT 测试。

成效

在同一测试中这种不拘一格的开箱测试和封闭式测试会产生低质量的测试 - 这些测试设定了不相关的预期,并且以被测软件不支持的方式编排和观察状态。

长期以来,对作为 Fuchsia 平台产品的系统测试工具进行忽视,导致了这一局面。不过,我们发现自己进行了许多不属于现有类别且不符合测试最佳实践的异域测试。

更糟糕的是,这些测试所依赖的平台实现细节在设计上无法进行演化。例如,大部分 FSI 都是用 FIDL 精确定义的。FIDL 旨在让平台开发者能够进行与 ABI 兼容的更改,或者检测现有 ABI 何时因给定更改而损坏。FIDL 为开发者提供了许多方法来在不破坏 ABI 的情况下更改类型和协议,或以可控方式引入破坏:稳定的协议方法序数、灵活的表、版本控制等。

这与使用进程名称作为协定(例如为了让测试终止进程)相对而言。由平台组件实现的进程的名称绝不会包含在 FSI 中,部分原因是没有进化功能 - 任何更改都是破坏性更改,无法进行版本控制,并且很少有可能使新旧进程同时运行,以实现临时兼容性窗口。这同样适用于全局文件系统路径、内部文件格式、自由文本日志和其他此类非协定。

缺少 OOT 支持

SL4F 客户端可以通过与通过 Fuchsia SDK 卷分发给 OOT 测试人员的平台映像中包含的 SL4F 守护程序进行通信,实现系统自动化。守护程序可以执行不同的任务来编排和检查划分到“ Facade”的系统状态。系统的这一方面通常表现良好,因为它是稳定的协定,并且有合理的方法可以随着时间的推移对其进行改进。

但是,开发 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 测试可以作为在测试领域运行的组件重新实现,以生成封闭单元测试或集成测试,我们将同样重新实现它们。为了启用并推广更健康的测试金字塔,我们将支持 OOT 组件测试,其方式等同于树内组件测试。

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

所有因访问真实系统服务而违反封闭性的现有旧版 CFv1 测试都将以符合相同或更高测试要求的其他条款重新实现。这些测试是作为封闭的 CFv2 组件测试重新实现、作为新的系统测试还是以其他形式重新实现,均由被测代码的所有者决定。

如果平台测试支持缺失或不足(例如,CFv1 组件所有者被阻止迁移到 CFv2),相关平台团队将与测试负责人合作,共同打造必要的平台解决方案。

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

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

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

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

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

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

减少并淘汰旧解决方案

平台团队和产品团队将展开合作,不再使用先前的解决方案,并在所有地方都采用新的安全型系统测试框架保持一致。我们不会长期使用旧版测试解决方案,除非提供了额外的激励措施来继续使用这些解决方案,例如在需要时用于实现跨平台测试套件兼容性。

优先级

我们可能会根据相关团队认为合适的话,优先处理相关工作。但是,我们建议您优先处理工作,先理清拥有和维护成本最高的测试,例如先攻击技术债务曲线的头部。

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

依赖项

为了弥补平台可测试性方面的差距,并开发和推出新框架,需要跨多个平台团队(包括组件框架、测试架构、SDK 工具、EngProd Testing 和 EngProd 基础架构)开展相关工作。

此外,所有产品合作伙伴团队和多个平台组件负责人都需要开展相关工作,以帮助发现可测试性缺陷并迁移到新的测试框架和解决方案。

风险和缓解措施

系统测试的范围并不限定于特定组件,也不是已封装的系统,而是限定为被测整个系统。因此,并没有严格定义实际要测试的代码。将系统测试从任何一个框架迁移到另一个框架后,原本作为系统测试附带优势而实现的有益测试覆盖率可能会丢失。

某些系统测试的范围非常大,这为它们的重新实现设定了非常高的标准。作为一个实用的预建步骤,缩小范围或拆分其中一些测试可能会有所帮助。

某些现有的系统测试没有专门的长期负责人。此类放弃软件可能难以使用,并且一些迁移工作不可避免地要由不同的团队承担。

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

支付技术债务与往常一样,会对团队的其他优先事项产生不利影响。为了有效执行这一转变,领导层之间必须达成一致。

不在范围内

树内系统测试

在树内开发的测试不在本文档的讨论范围之内。尽管上述工作可能会随机抽查到树内系统测试,但由于树内测试的问题陈述明显不同,因此本文未作讨论。例如,树内测试可以在 Fuchsia 平台接口下运行,而不会对 Fuchsia 的可更新性原则和目标产生负面影响。

组件测试

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

特殊可移植性要求

在少数特殊情况下,您需要在 Fuchsia 上运行现有测试套件,以证明与其他实现的兼容性或合规性,或对其进行基准测试。在这种情况下,现有测试必须原封不动地运行,否则将无法放心地证明兼容性。进而从根本上定义了设备端测试自动化系统的规范。

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

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

只要有正确的理由运用正确的方法、有条不紊地运用问题,这些解决方案就适合这个独特的问题领域。作为反例,如果我们将 LLVM 测试框架移植到 Fuchsia,然后使用此框架编写了超出或证明兼容性测试范围或与合作伙伴项目共享测试范围之外的新测试,这就需要其他理由,或者平台团队不建议这样做。