- 项目主管: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 平台。之所以这样做,是因为平台组件大多不提供支持的方法来执行满足产品测试需求所需的插桩。测试作者已经找到了一些方法来规避这些限制,但这些解决方案会导致测试质量不佳。
如果将 Fuchsia 平台的开发流程置于能够规避平台协定的 OOT 测试之下,将会威胁 Fuchsia 的可更新能力。
什么是系统测试?
系统测试是对已组装完毕的系统进行的测试,目的是验证系统是否满足特定要求。在实用测试金字塔中,系统测试是对单元测试和集成测试的补充,用于填补测试缺口,而这些缺口只能通过观察完整的受测系统来解决。
系统测试有时也称为:
端到端 (e2e) 测试,尤其是当系统测试的范围超出特定目标设备,还涵盖例如通过网络接口的远程服务器或控制主机时。
关键用户体验历程 (CUJ) 测试,尤其是当测试以模拟和自动化用户输入和输出来表示时,例如通过注入按钮按下等输入事件,并将界面状态摘要或屏幕截图与预期的测试结果进行比较。
此外,这些测试还可以进一步插桩以产生额外的价值,如下所示:
性能测试:除了运行产品 CUJ 之外,测试 harness 还会收集性能信息,例如时间、轨迹、FPS 统计信息等。
长寿命测试,其中在紧密循环中运行相同的 CUJ,并监控系统是否出现资源泄露(RAM、句柄等)或崩溃等压力迹象。
此外,还有一整类系统测试,用于演练平台功能并在树中开发。这些问题不在本文档的讨论范围内。
Fuchsia 目前面临的系统测试挑战
非封闭式旧版 (v1) 组件测试
Fuchsia 的组件框架提供全面的测试支持,可在测试环境与系统的其余部分之间实现高度隔离。旧版 (CFv1) 测试仍受支持,可用于测试尚未迁移到 CFv2 的旧版组件。虽然大多数生产组件仍采用 CFv1,但大多数测试(超过 70%)使用 CFv2 测试框架,因为它更可靠,并为开发者提供了一些新功能。
出于旧版原因,v1 测试运行时在许多方面都不具备完全隔离性,例如允许访问某些真实的系统服务。因此,许多 CFv1 测试实际上会意外地表现为系统测试。这些测试存在多种问题,包括:
由于测试的完整范围非常广泛或未严格定义,因此很难排查测试失败问题。
测试可能会受到外部状态的影响,或者会将外部状态作为副作用保留。因此,这些测试可能会与其他此类系统测试产生交叉干扰,导致无法重现的失败(“片状”)或在不稳定条件下重现的失败(例如,通过以不同的顺序重新运行相同的测试)。
假定其他系统组件的实现细节超出其声明的协定范围的测试。
组件测试框架旨在用于编写隔离的密封单元测试和集成测试。在这种测试中,任何失败原因都只能来自测试环境。由于这种隔离性预期,我们还预计任何两个测试都可以并发运行或按任何顺序运行,而不会影响其结果。使用已废弃的 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 提供的相同的演变机制。您可以通过引入新的外观来扩展 SL4F 的自动化功能,但所有外观都必须在树中开发和构建。因此,在为定义和开发 OOT 的产品测试 CUJ 时,使用 SL4F 会导致下文中列出的一些问题。
允许进行过于侵入性测试的另一种常见机制是使用 SSH 获取远程 shell 并在主机和目标之间复制文件。这与将 SSH 用作隧道协议的情况不同,后者例如可用作 Overnet 的传输协议。
目前,Fuchsia 的工程 build 包含一个 SSH 守护程序,该守护程序在运行时会对全局命名空间拥有不受沙盒限制的访问权限,并向客户端提供 dash
shell。同一守护程序还允许 SCP 功能对全局命名空间(例如全局可变存储空间)具有类似程度的读写访问权限。这常常是一种规避 FSI 的方法,允许测试作者通过在可变存储空间中观察或更改平台组件的状态来违反平台组件支持的接口,并在此过程中依赖于平台实现细节,例如 Fuchsia 基础软件包的名称。
SSH 和 SCP 访问已关联,以便测试作者轻松使用,例如在 Dart 中使用 SL4F 客户端库。
脆弱的测试模式
SL4F 为测试作者提供了多种操控和观察系统状态的方法。其中一些机制会绕过平台产品协定和必要的抽象层。在编写 SL4F 测试时,使用这些机制并非绝对必要,并且并非所有测试都必须使用这些机制。但这些测试的存在会导致许多反模式进入我们的 SL4F 测试目录。
值得注意的是,由于平台未能提供可靠的替代方案来满足测试需求,因此这些模式是迫不得已而开发的。我们列出以下模式,并不是要批评平台开发者或测试作者,而是为了了解和分类我们目前必须偿还的技术债务。
通过非协定观察状态
被测代码可能会将其状态信息发送到系统日志或通过检查功能发送。这些工具非常有用,可用于将诊断信息收集到快照中。但它们不是合同。FIDL 在 Fuchsia 上用于定义可稳定且具有演化机制(例如对线格格式和版本进行二进制兼容更改)的强类型协定,这允许未协调的客户端和服务器交换 FIDL 消息。FIDL 是专为此目的精心设计的,而自由文本日志和 Inspect 则不是。
一些值得注意的具体示例
测试中的日志:某些长寿命测试使用严重程度级别为“错误”或更高级别的注释日志,以确定产品在测试期间是否受到了意外的压力。遗憾的是,从测试作者的角度来看,测试执行期间发出的“错误”消息通常实际上是良性的。因此,长寿测试作者不可避免地需要维护已记录错误消息的许可名单。
在测试中检查:某些测试驱动程序会从其驱动的组件读取 Inspect 信息,以观察这些组件的状态。由于 Inspect 是类型化数据,并且可以作为单个一致的快照获取,因此对于组件的作者来说,它是一款用于诊断组件当前状态的实用工具。但是,如果将其用作平台组件和产品测试之间的协定,则会导致 ABI 不稳定,并经常导致故障。这些破坏很难排查,因为它们可能会在底层平台更改生效数周后,在尝试发布 SDK 时发生。
通过非协定操控状态
SL4F 测试可以对主机进行大量控制,包括在未沙盒化的 shell 中执行任意 SSH 命令(即在全局命名空间中执行),以及对全局不可变存储空间和可变存储空间拥有完整的读写权限。以下是一些关键示例:
终止和重启进程:这通常用于测试中的设置和拆解例程。intent 是积极的 - 测试希望清除所有之前的状态并从头开始。不过,测试的平台组件未必经过测试,无法确保在多次重启或以不同的顺序重启时能够正常运行,这通常会导致不稳定的行为。
这种方法的另一个问题是,通过让 OOT 测试终止平台进程,进程名称(不是平台协定的一部分)会成为协定。这种违反预期接口和协定的行为会使平台重构变得更加困难,并使 OOT 测试变得更加脆弱。
操纵可变存储空间:此明显的沙盒违规行为通常用于将用户凭据作为某些 CUJ 测试的设置步骤注入。该方法不是操作用于凭据注入的预期接口,而是在受测代码读取所述状态之前注入状态。如果时间不正确,测试将失败。如果清理失败,后续测试可能会因受到测试串扰而失败。
全局可变文件系统访问的另一个用例是将全局 /tmp
存储目录用作测试结果、工件和诊断信息的暂存区,例如测试期间收集的性能轨迹。同样,这也是测试失败清理状态、通过串扰相互影响或通过其他方式注入虚假或不稳定行为的机会。
不同组件实例的隔离存储目录在同一分区上进行管理,因为为不同组件创建单独的分区既费时又不灵活。隔离是通过在共享分区上创建特定的目录布局来实现的。目录布局反映了平台实现细节,例如组件拓扑或组件管理器如何将该拓扑转换为文件系统部分。再次强调,这些是平台实现细节,可能会随时间而变化,不应在 OOT 测试中公开。
成效
在同一测试中混合使用开箱测试和闭箱测试会产生低质量的测试,即设置不相关的预期,并以被测软件未设计为支持的方式编排和观察状态。
目前的情况是由于长期以来忽视了作为 Fuchsia 平台产品的系统测试工具。尽管如此,我们发现自己还有很多不属于现有类别且不符合测试最佳实践的特殊测试。
更糟糕的是,这些测试已开始依赖的平台实现详情采用的术语并非旨在支持演变。例如,FSI 的大部分内容都以 FIDL 的形式精确定义。FIDL 旨在让平台开发者能够进行与 ABI 兼容的更改,或检测给定更改何时会破坏现有 ABI。FIDL 为开发者提供了多种方法来更改类型和协议,而不会破坏 ABI,或者以受控方式引入破坏:稳定的协议方法序数、灵活的表格、版本控制等。
与此相反,您也可以将进程名称用作协定,例如,让测试终止进程。由平台组件实现的进程的名称绝不会包含在 FSI 中,这在一定程度上是因为没有任何进化途径 - 任何更改都是破坏性更改,没有版本控制途径,并且很少能同时运行旧进程和新进程以实现临时兼容性窗口。同样的,全局文件系统路径、内部文件格式、自由文本日志和其他此类非协定也是如此。
缺少 OOT 支持
SL4F 客户端可以通过与包含在通过 Fuchsia SDK 分发给 OOT 测试人员的平台映像中的 SL4F 守护程序通信来实现系统自动化。守护程序可以执行不同的任务,以协调和检查分组为“外观”的系统状态。系统的这一方面通常运行良好,因为它是一种稳定的协定,并且有合理的方法可以随着时间的推移对其进行演变。
不过,外观只能在树内开发,这意味着 OOT 目前无法扩展系统自动化功能。这并不是 SL4F 的意外属性,它根本就不是为 OOT 客户端设计的。
单独的堆栈
SL4F 提供了配置、设备发现、主机-目标网络传输协议、一些可扩展性机制以及向 SDK 客户提供客户端库和工具的解决方案。
ffx
也解决了同样的问题,而且可以说采用了更好的方法。开发 ffx
插件时,如果宿主端插件需要目标端协作组件来控制目标,则可以开发 FIDL 代理。然后,以 FIDL 的形式定义主机和目标之间的通信。与通过 HTTP 的 JSON-RPC 不同,FIDL 可以是 FSI 的一部分,并且可以作为与平台和 SDK 的其余部分的协定而演变。这在一定程度上是因为,使用 FIDL 维护 ABI 兼容性比使用架构仅存在于代码中的无类型 JSON 协定更容易。
与此相关的是,ffx
堆栈可以更好地了解 Fuchsia 组件,例如知道何时以及如何启动测试所需的组件。它还可以解决 SL4F 堆栈根本无法解决的问题,例如主机-目标身份验证。这样一来,您就可以在 userdebug
build 类型中使用 ffx
,而 SL4F 仅允许在 eng
build 中使用。
其他自定义测试框架
除了上述解决方案之外,还有其他 OOT 测试解决方案。这些测试涵盖了各种测试,但它们主要作为系统测试进行编排,因为它们不会使用平台自己的隔离机制进行测试。
部分 OOT 合作伙伴共用一组测试解决方案和工具,但使用方式不一致。这导致技术债务不断增加。
解决方案陈述
Fuchsia 平台团队将在树内和 OOT 中创建强大的系统测试解决方案。平台团队将与产品团队合作,了解产品测试需求并满足这些需求,并协助产品团队进行任何迁移。
新解决方案将采用组件框架和 ffx
插件的沙盒功能,以实现以下目标:
- 让您能够在 OOT 中编写出色的测试,包括出色的系统测试。
- 使编写违反 Fuchsia 平台严格定义且受支持的协定的 OOT 测试变得更加困难(几乎不可能)。
具体而言:
提倡在适用情况下进行组件化单元测试和集成测试
如果 OOT 测试可以重新实现为在测试领域中运行的组件,以生成密封单元测试或集成测试,我们将以这种方式重新实现它们。为了实现和推广更健康的测试金字塔,我们将支持 OOT 组件测试,使其与树内组件测试具有同等地位。
重新实现所有非封闭 CFv1 测试
所有现有的旧版 CFv1 测试如果通过访问真实系统服务而违反了密封性,都将以满足相同或更高测试要求的其他方式重新实现。这些测试是作为密封 CFv2 组件测试、新的系统测试还是以其他形式重新实现,取决于被测代码的所有者。
如果缺少平台测试支持或支持不足(例如 CFv1 组件所有者被阻止迁移到 CFv2),相关平台团队将与测试所有者合作,共同打造必要的平台解决方案。
为产品所有者创建新的系统测试平台解决方案
Fuchsia 平台团队将开发一种新解决方案,以便产品所有者编写符合以下条件的系统测试:
系统测试可在 OOT 环境中开发和执行。
调用这些测试并收集其结果只有一种方式,可确保在所有环境中遵循相同的工作流并获得一致的结果,无论是在本地开发者工作流中执行测试,还是在某些 CI/CQ 自动化流程中执行相同的测试(无论使用哪种自动化解决方案)。
平台的详细信息(例如 Fuchsia 系统接口中定义的内容)如果不属于预期合同的一部分,则不会向 OOT 系统测试开发者公开。为了实现所需的沙盒级别,系统会将测试和测试框架迁移到 CFv2(如果尚未迁移)。
系统测试框架会与
ffx
共享尽可能多的堆栈。这包括配置、主机工具和客户端库分发机制、版本控制、配置、目标设备发现、主机-目标身份验证、主机-目标传输以及远程控制机制等。平台系统组件的所有者可以并将会扩展系统测试框架,以满足产品开发者的测试需求。这样一来,平台开发者将创建可维护的 ABI,并为这些可测试性协定建立长期所有权。
减少和淘汰旧版解决方案
平台团队和产品团队将通力合作,逐步淘汰旧版解决方案,并在所有平台上采用新的受祝福的系统测试框架。旧版测试解决方案不会长期使用,除非有特殊的激励因素继续使用它们,例如在需要时实现跨平台测试套件兼容性。
优先级
工作优先级可由相关团队自行确定。不过,我们建议您优先解决最耗费资源的测试,例如先解决技术债务曲线的顶部问题。
无论具体选择哪些任务,在考虑可以采取哪些措施来偿还技术债务时,系统测试现代化工作都将被视为高优先级任务。
依赖项
为了缩小平台可测试性差距并开发和发布新框架,需要跨多个平台团队(包括组件框架、测试架构、SDK 工具、EngProd 测试和 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,然后使用此框架编写了新测试,但这些测试超出了证明兼容性或与合作伙伴项目共享测试的范围,则需要提供额外的理由,否则平台团队会不鼓励这样做。