测试因而而异的最常考虑的参数是范围(有时也称为“大小”)。
范围相对于可测试单元而言。侧重于单个此类单元的测试称为单元测试,而同时测试多个单元的测试称为集成测试。大多数测试也可以按测试金字塔的形式排列。
单元是从测试作者的角度任意定义的。例如,在面向对象的编程中,单元通常是类。在本文档中,以及在 Fuchsia 中,除非另有说明,否则通常会提到被测单元是组件。
在为 Fuchsia 编写测试时,请确保您熟悉编写测试的测试原则和最佳实践。
测试范围
不同范围的测试各有优势和不足,相辅相成。合理搭配测试可以制定出出色的测试计划,让开发者能够放心地进行更改,并快速高效地捕获真实 bug。
在考虑要编写哪些类型的测试以及每种测试的数量时,请想象测试是按金字塔形排列的:
许多软件测试出版物都建议采用 70% 单元测试、20% 集成测试、10% 系统测试的组合。Fuchsia 建议在集成测试方面投入更多精力,而忽略其他类型的测试,原因如下:
- Fuchsia 强调软件的组件化。Fuchsia 上的应用和系统往往会非常广泛地使用组件之间的边界。因此,我们建议您测试这些边界。
- 借助 Fuchsia 的组件框架,您有时可以轻松地在测试中重复使用生产组件,从而降低开发集成测试的成本。
- Fuchsia 的测试运行时为集成测试提供了与单元测试相同的隔离级别,使集成测试同样可靠。
- 组件之间的通信速度比直接方法调用慢几个数量级,但仍然非常快。集成测试的运行速度通常不会比单元测试慢到人为开发者可以察觉到的程度。
- Fuchsia 上已经有许多集成测试可用作实用示例。
如需详细了解 Fuchsia 中测试金字塔中的各种测试,请参阅以下内容:
还有一些不在测试金字塔范围内的专门测试。如需了解详情,请参阅专门的测试。
另请参见:
单元测试
在 Fuchsia 上,在单个组件中完全定义的测试称为单元测试。
在隔离的 realm 中运行,该 realm 充当其沙盒。这样可以防止测试获得意外功能,防止测试受到测试预期范围之外的系统状态的影响,以及防止测试影响在同一设备上并行或顺序运行的其他测试。
单元测试由测试正文中的代码直接调用来控制。例如,audio_effects_example_test.cc
会在可加载模块中运行代码,该模块实现音效并验证代码是否有效。
在健康的测试金字塔中,单元测试是测试的广泛基础,因为单元测试可提供最高程度的隔离,并且是速度最快的测试,这反过来会产生非常可靠且有用的正信号(测试通过)和负信号(测试失败,并出现可操作的错误,表明存在实际缺陷)。单元测试还会生成最具实用价值的测试覆盖率信息,并可享受其他附带好处,例如与sanitizer 结合运行时可获得的优势。因此,任何可通过单元测试满足的测试需求都应通过单元测试满足。
如果只有测试包中的内容会影响测试能否通过,则单元测试被称为“密封”测试。
另请参见:
集成测试
在 Fuchsia 上,跨多个组件(这些组件在单个测试软件包中提供)的测试称为集成测试。
在健康的测试金字塔中,集成测试是仅次于单元测试的第二大常见测试类型。Fuchsia 上的集成测试通常速度很快,但不如单元测试。与单元测试一样,Fuchsia 上的集成测试会与系统的其余部分隔离,不受外部影响。与单元测试相比,集成测试在定义和驱动方面更为复杂。因此,如果单元测试无法满足某项测试需求,但集成测试可以满足,则应使用集成测试来满足该需求。
与其他平台相比,集成测试在 Fuchsia 中发挥着更重要的作用。 这是因为 Fuchsia 鼓励软件开发者将其代码分解为多个组件,以实现平台的安全和可更新目标,然后提供强大的组件间通信机制,例如通道和 FIDL。
在集成测试中,创建 realm 的方式与单元测试类似,但随后会在该 realm 中暂存多个组件,并使这些组件相互交互。测试由测试领域中的某个组件驱动,该组件还会确定测试是通过还是失败,并提供其他诊断信息。例如,字体服务器集成测试会测试将 fuchsia.pkg.FontResolver
FIDL 协议作为协议功能公开的字体解析器组件,然后通过调用该组件与客户端的合约中的方法并验证预期响应和状态转换来验证该组件。
集成测试由测试驱动程序控制。测试驱动程序会设置一个或多个受测组件,对其进行演练,并观察结果,例如使用受支持的接口和其他协定查询这些组件的状态。
在测试 realm 中,标准 capability 路由的运作方式与在任何其他 realm 中的运作方式完全相同。在测试 realm 中,capability 路由通常用作依赖项注入机制。例如,如果被测组件依赖于测试范围之外的其他组件提供的功能,则可以针对其他组件(“测试双重”)进行测试,也可以针对在测试环境中单独运行的正式版组件的单独实例进行测试。
如果只有测试软件包的内容会影响测试能否通过,则集成测试被称为“密封”。
另请参见:
系统测试
在 Fuchsia 上,范围不限于单个软件包及其包含的一个或多个组件的测试称为系统测试。此类测试的范围并未严格定义为组件或软件包,并且可能涵盖给定配置中构建的整个系统。
系统测试有时也称为关键用户历程 (CUJ) 测试或端到端 (E2E) 测试。有些人可能会说,如果测试范围甚至超出了单个 Fuchsia 设备(例如,针对远程服务器进行测试,或者在存在特定 Wi-Fi 接入点或其他已连接硬件的情况下进行测试),则此类测试属于端到端测试。
在健康的测试金字塔中,系统测试是金字塔的尖端,表示系统测试的数量比其他范围的测试要少。系统测试通常比单元测试和集成测试慢得多,因为它们会运行更多代码,并且具有更多依赖项,其中许多依赖项是隐式定义的,而不是显式声明的。系统测试无法提供固有的隔离程度,并且会受到更多副作用或意外干扰的影响,因此通常不如其他类型的测试可靠。由于系统测试所执行代码的范围未定义,Fuchsia 的 CI/CQ 不会从端到端测试收集测试覆盖率(收集覆盖率会产生不稳定或不稳定的结果)。出于类似原因,sanitizer 不支持端到端配置。因此,如果单元测试或集成测试无法获得系统测试的边际效益,您应编写系统测试。
其他测试
Fuchsia 中提供了各种专用测试:
内核测试
由于涉及的特殊要求和约束条件,因此需要特别注意测试内核。内核测试涵盖内核的表面:系统调用、内核机制(例如句柄权限或信号对象)、vDSO ABI 和用户空间引导,以及所有重要的实现细节。
内核的大多数测试都在用户空间中运行,并从外部测试内核 API。这些测试称为核心用户模式测试。例如,TimersTest
用于测试内核的计时器 API。与常规测试相比,这些测试的编写难度更高:仅支持某些编程语言、测试之间的隔离边界较弱、测试无法并行运行、无法在不重新启动和重新铺路的情况下更新测试,并且还存在其他约束条件和限制。通过这些权衡,测试对其运行时环境的假设会减少,这意味着即使在没有组件框架或高级网络功能的最基本 build 配置(启动 build)中,这些测试也能运行。这些测试内置于 zbi 中。您可以运行这些测试,使其在用户模式下不运行任何其他程序,这有助于隔离内核 bug。
某些内核行为无法从外部进行测试,因此需要使用编译到内核映像并在内核空间中运行的代码进行测试。这些测试的编写和问题排查难度更高,但对内核内部的访问权限最高,即使用户空间引导失败,也能运行。有时,这对于测试来说是必要的。例如,timer_tests.cc
会测试计时器截止期限,而仅通过在用户空间中调用内核 API 无法准确地测试这些期限,因为内核在合并从用户空间设置的计时器时会预留一些空闲时间。
兼容性测试
兼容性测试(有时称为一致性测试)将正确性的概念扩展为满足给定规范并随着时间的推移遵循该规范。兼容性测试以特定的合同条款表示,可针对该合同的多个实现运行,以确保任意数量的实现都具有兼容性。
例如:
- Fuchsia 兼容性测试 (CTF) 是用于验证 Fuchsia 系统接口的测试。CTF 测试可针对不同版本的 Fuchsia 运行,以确保它们保持兼容性或检测破坏性更改。最终,CTF 测试将涵盖 Fuchsia SDK 的整个 Surface。
- FIDL 使用特定的二进制格式(或线格格式)对通过通道在组件之间交换的 FIDL 消息进行编码。FIDL 客户端和服务器端绑定和库以许多语言(例如 C、C++、Rust、Dart 和 Go)提供。FIDL 兼容性测试和 Golden FIDL (GIDL) 测试可确保不同的实现具有二进制兼容性,这样无论开发者选择哪种编程语言,客户端和服务器都可以正确且一致地相互通信。
- Fuchsia Inspect 使用特定二进制格式来编码诊断信息。“检查”功能支持多种语言,并且底层二进制格式可能会随时间而变化。检查验证器测试,确保所有读取器/写入器实现之间的二进制兼容性。
性能测试
性能测试会运行特定工作负载(例如合成基准测试或 CUJ),并衡量测试期间的性能指标,例如完成时间或资源使用情况。
性能测试可以确保达到特定阈值(例如,帧渲染速率保持在 60 FPS 的一致水平),也可以仅收集性能信息并将其作为结果呈现。
例如:
- 您可以在端到端性能测试目录中找到许多性能测试和性能测试实用程序。这些测试涵盖了内核启动时间、触控输入延迟时间、Flutter 渲染性能以及对性能敏感的代码的各种微基准等一系列目标。
- CPU 性能监控可让 CPU 性能计数器可用于高频率低开销性能测试。
- Perfcompare 是一个框架,用于比较给定更改前后给定基准的结果。这对于衡量性能改进或回归非常有用。
- FIDL 基准测试包含适用于 FIDL 生成的代码的各种大小的基准测试。
- 网络堆栈基准测试使用热点代码的微基准测试网络堆栈的性能。
压力测试和长时测试
压力测试会以极高负载的方式运行系统,但在系统协定中,这种情况属于正常现象。其目的是证明系统对压力的弹性,或揭示仅在压力下发生的 bug。
在压力测试中,受测系统会与测试工作负载组件隔离。测试工作负载组件可以执行任何操作,包括崩溃,而不会使被测系统崩溃。
例如,minfs 压力测试会反复对 MinFS 分区执行许多操作。该测试可以验证操作是否成功、观察到的结果是否符合预期,以及 MinFS 是否会崩溃或出现文件系统损坏等其他错误。
有意长时间运行系统的测试是一种称为长时测试的压力测试。在长寿命测试中,时间跨度与系统上的负载或压力密切相关。例如,如果系统成功完成 10,000 次用户体验历程,则属于压力测试;如果系统在 20 小时内循环完成相同的用户体验历程,并且始终保持正确且响应迅速,则也属于长效性测试。
另请参见:
模糊测试
fuzz 工具是指尝试与待测代码进行有效交互的程序,这类交互会导致错误情况(例如崩溃)。模糊测试由伪随机行为引导,有时还会通过插桩(寻找涵盖新代码路径的输入)或其他有根据的策略(例如预生成的字典或机器学习)来引导。
经验表明,模糊测试工具可以发现独特的高严重性 bug。例如,reader_fuzzer.cc
在 Inspect 读取器代码中发现了会导致运行时崩溃的 bug,即使此代码已通过大量测试和过滤器。
另请参见: