树外系统测试支持

  • 项目负责人: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 测试实际上就像系统测试一样运行。这些测试存在多个问题,包括:

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

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

  • 对声明的协定之外的其他系统组件的实现细节进行的测试。

  • 组件测试框架旨在用于编写隔离的封闭单元测试和集成测试。在此类测试中,任何失败原因都只能来自测试领域。由于有这种隔离预期,因此任意两项测试都可以并发或按任意顺序运行,而不会影响结果。如果使用已废弃的 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 传输协议很实用)混淆。

目前,Fucchsia 的工程 build 包含一个 SSH 守护程序,该守护程序可以通过不经过沙盒屏蔽的全局命名空间访问权限运行,并向客户端提供 dash shell。这一守护程序还允许 SCP 功能对全局命名空间(例如全局可变存储空间)进行类似程度的读/写访问。 很多时候,这都是一种规避 FSI 的方式,允许测试作者通过观察或更改可变存储空间中的状态来破坏平台组件的受支持接口,并在此过程中依赖平台实现细节(例如 Fuchsia 基础软件包的名称)。

将 SSH 和 SCP 访问连接起来,以便测试作者使用(例如,使用 Dart 中的 SL4F 客户端库)。

脆弱测试模式

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

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

通过非协定观察状态

被测代码可能会将其状态信息发送到系统日志或通过检查发出。这些工具有助于将实例诊断信息收集到快照中。不过,它们并非合同形式。在 Fuchsia 上使用 FIDL 来定义强类型协定,这些协定可以稳定且具有演化机制,例如对传输格式和版本控制进行二进制兼容更改,从而允许不协调的客户端和服务器交换 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 只能在树内完成,这意味着 OOT 目前无法扩展系统自动化功能。这对于 SL4F 来说并不奇怪,因为其设计初衷并非供 OOT 客户端使用。

单独的堆叠

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

ffx 也解决了同样的问题,或许能以更好的方式解决。在开发 ffx 插件时,如果主机端插件需要目标端协作组件来对目标施加控制,则可以开发 FIDL 代理。然后,根据 FIDL 定义主机和目标之间的通信,FIDL 与基于 HTTP 的 JSON-RPC 不同,该程序可以是 FSI 的一部分,并且可以作为与平台其余部分和 SDK 的合同进行演变。这在一定程度上是因为使用 FIDL 来保持 ABI 兼容性比使用非类型 JSON 协定(架构仅位于代码中)更简单。

相应地,ffx 堆栈对 Fuchsia 组件有更深入的了解,例如,知道何时以及如何启动测试所需的组件。它还解决了 SL4F 堆栈甚至不关注的问题,例如主机-目标身份验证。这样就可以在 userdebug build 类型中使用 ffx,而 SL4F 只能在 eng build 中使用。

其他自定义自动化测试框架

还有未在上方列出的 OOT 测试解决方案。这些测试涵盖方方面面的测试,尽管它们在很大程度上被编排为系统测试,因为它们不使用平台自己的隔离机制进行测试。

某些 OOT 合作伙伴会共用一组相同的测试解决方案和工具,但部分方式不一致。这导致技术债务不断增长。

解决方案声明

Fuchsia 平台团队将在树内和 OOT 中打造强大的系统测试解决方案。平台团队将与产品团队合作,以了解产品测试需求,满足需求,并协助产品团队推动的任何迁移。

新解决方案将支持组件框架ffx 插件的沙盒功能,以便:

  1. 编写出色的 OOT 测试(包括出色的系统测试)成为可能。
  2. 使编写违反 Fuchsia 平台严格定义和受支持合约的 OOT 测试变得更加困难(甚至不可能)。

具体而言:

尽可能推广组件化单元测试和集成测试

如果 OOT 测试可以作为在测试领域运行的组件重新实现,以生成封闭的单元测试或集成测试,我们将如此重新实现。为了实现并促进更健康的测试金字塔,我们将支持 OOT 组件测试,与树内组件测试相同。

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

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

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

为产品负责人打造新的系统测试平台解决方案

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

  2. 只有一种方法可以调用这些测试并收集其结果,以确保遵循相同的工作流并在所有地方实现相同的结果,无论测试在本地开发者工作流中执行,无论开发者环境如何,或者是否在某些 CI/CQ 自动化中执行相同的测试(无论使用哪种自动化解决方案)。

  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 升级到更高级别的工具链支持。

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

对于这个独特的问题空间而言,这些解决方案就是很好的解决方案,前提是这些方案有正确的理由并有条不紊地使用。作为反例,如果我们将 LLVM 测试框架移植到 Fuchsia,然后使用此框架编写了新的测试,而这些测试超出了演示兼容性或与合作伙伴项目共享测试的范围,那么将需要额外的理由,或者平台团队不建议这样做。