自动化测试可以通过多种方式帮助您提高应用质量。例如,它有助于执行验证、捕获回归错误以及验证兼容性。一套良好的测试策略让您可以利用自动化测试来实现一个重要的目标:提高开发者生产力。
当团队将系统的测试方法与基础设施改进相结合时,能够达到更高的生产力水平。这样做可以针对代码的行为提供及时的反馈。一套良好的测试策略应具备以下特点:
- 尽早捕获问题。
- 执行速度快。
- 在需要修复时提供明确的指示。
本页面将帮助您决定实施哪些类型的测试、在何处运行以及运行频率。
测试金字塔
您可以按规模对现代应用中的测试进行分类。小型测试仅关注代码的一小部分,因此速度快且可靠。大型测试范围广泛,需要复杂的设置,维护起来比较困难。不过,大型测试具有更高的保真度*,并且能够一次性发现更多问题。
*保真度是指测试运行时环境与生产环境的相似程度。
大多数应用应该包含许多小型测试和相对较少的大型测试。各类测试的分布应形成一个金字塔,数量众多的小型测试构成底部,数量较少的大型测试构成顶端。
最小化缺陷成本
一套良好的测试策略能够在最大限度提高开发者生产力的同时,将发现缺陷的成本降至最低。
以一种可能效率低下的策略为例。在这种情况下,测试规模的分布没有形成金字塔。存在过多的端到端大型测试,而组件 UI 测试过少。
这意味着在合并代码之前运行的测试太少了。如果存在缺陷,测试可能要等到夜间或每周的端到端测试运行时才能捕获到。
考虑这对识别和修复缺陷的成本意味着什么,以及为什么将测试工作偏向于更小、更频繁的测试非常重要,这一点至关重要。
- 当缺陷被单元测试捕获时,通常几分钟内就能修复,因此成本很低。
- 端到端测试可能需要几天时间才能发现同一个缺陷。这可能会牵涉到多个团队成员,降低整体生产力,并可能推迟发布。这种缺陷的成本更高。
话虽如此,一套效率低下的测试策略也比完全没有策略要好。当缺陷进入生产环境时,修复程序需要很长时间才能到达用户的设备,有时甚至需要几周,因此反馈循环最长,成本也最高。
可扩展的测试策略
测试金字塔传统上分为 3 类:
- 单元测试
- 集成测试
- 端到端测试。
然而,这些概念并没有精确的定义,因此团队可能希望以不同的方式定义其类别,例如使用 5 层结构:
- 单元测试在宿主机上运行,验证单个功能逻辑单元,且不依赖于 Android 框架。
- 示例:验证数学函数中的差一错误 (off-by-one errors)。
- 组件测试验证模块或组件的功能或外观,使其独立于系统中的其他组件。与单元测试不同,组件测试的范围扩展到了方法和类之上的更高抽象层级。
- 示例:针对自定义按钮的截屏测试。
- 功能测试验证两个或多个独立组件或模块之间的交互。功能测试规模更大、更复杂,通常在功能级别运行。
- 示例:验证屏幕状态管理的 UI 行为测试。
- 应用测试以可部署二进制文件的形式验证整个应用的功能。它们是大型集成测试,使用可调试的二进制文件(例如可能包含测试钩子的开发构建版本)作为被测系统。
- 示例:用于验证折叠屏配置更改、本地化和无障碍功能的 UI 行为测试。
- 发布候选 (Release Candidate) 测试验证发布版本的功能。它们与应用测试类似,不同之处在于应用二进制文件是经过压缩和优化的。这些是大型端到端集成测试,运行在尽可能接近生产环境的环境中,且不会将应用暴露给公开的用户帐户或后端。
- 示例:关键用户路径、性能测试。
这种分类考虑了保真度、时间、范围和隔离级别。您可以在多个层级中使用不同类型的测试。例如,应用测试层可以包含行为测试、截屏测试和性能测试。
作用域 |
网络访问 |
执行 |
构建类型 |
Lifecycle |
|
|---|---|---|---|---|---|
Unit |
具有最少依赖关系的单个方法或类。 |
否 |
本地 |
可调试 |
合并前 |
组件 |
模块或组件级别 多个类组合 |
否 |
本地 |
可调试 |
合并前 |
功能 |
功能级别 与由其他团队拥有的组件集成 |
模拟 |
本地 |
可调试 |
合并前 |
应用程序 (Application) |
应用 (Application) 级别 与由其他团队拥有的功能和/或服务集成 |
模拟 |
模拟器 |
可调试 |
合并前 |
版本候选版 (RC) |
应用 (Application) 级别 与由其他团队拥有的功能和/或服务集成 |
生产服务器 |
模拟器 |
压缩后的发布构建 |
合并后 |
确定测试类别
根据经验,您应该考虑金字塔中能为团队提供正确反馈级别的最低层级。
例如,考虑如何测试此功能的实现:登录流程的 UI。根据您需要测试的内容,您将选择不同的类别:
被测对象 |
被测内容的描述 |
测试类别 |
示例测试类型 |
|---|---|---|---|
表单验证逻辑 |
一个通过正则表达式验证电子邮件地址并检查密码字段是否已输入的类。它没有依赖项。 |
单元测试 |
|
登录表单 UI 行为 |
一个带有按钮的表单,只有在表单通过验证时按钮才会启用。 |
组件测试 |
在 Robolectric 上运行的 UI 行为测试 |
登录表单 UI 外观 |
符合 UX 规范的表单。 |
组件测试 |
|
与 Auth Manager 集成 |
将凭据发送给 auth manager 并接收可能包含不同错误的响应的 UI。 |
功能测试 |
|
登录对话框 |
按下登录按钮时显示登录表单的屏幕。 |
应用测试 |
在 Robolectric 上运行的 UI 行为测试 |
关键用户旅程:登录 |
使用测试帐户针对预发布服务器的完整登录流程。 |
版本候选版 (RC) |
在设备上运行的端到端 Compose UI 行为测试 |
在某些情况下,某项测试属于哪个类别可能是主观的。将测试向上或向下移动可能有其他原因,例如基础设施成本、不稳定性 (flakiness) 和长时间的测试时间。
请注意,测试类别并不决定测试的类型,也并非所有功能都必须在每个类别中进行测试。
手动测试也可以作为您测试策略的一部分。通常,QA 团队执行发布候选测试,但他们也可以参与其他阶段。例如,在没有脚本的情况下探索性测试功能中的缺陷。
测试基础设施
测试策略必须有基础设施和工具的支持,以帮助开发者持续运行测试,并执行强制所有测试通过的规则。
您可以按范围对测试进行分类,以定义何时以及在何处运行哪些测试。例如,遵循 5 层模型:
类别 |
环境 (何处) |
触发器 (何时) |
|---|---|---|
Unit |
[本地][4] |
每次提交 |
组件 |
本地 |
每次提交 |
功能 |
本地和模拟器 |
合并前,在合并或提交更改之前 |
应用程序 (Application) |
本地、模拟器、1 台手机、1 台折叠屏设备 |
合并后,在合并或提交更改之后 |
版本候选版 (RC) |
8 种不同的手机、1 台折叠屏设备、1 台平板电脑 |
发布前 |
- 单元测试和组件测试在持续集成系统上针对每次新提交运行,但仅针对受影响的模块。
- 所有单元测试、组件测试和功能测试在合并或提交更改之前运行。
- 应用测试在合并后运行。
- 发布候选测试每晚在手机、折叠屏设备和平板电脑上运行。
- 发布前,发布候选测试会在大量设备上运行。
当测试数量影响生产力时,这些规则可以随时间调整。例如,如果您将测试改为夜间运行,可能会减少 CI 构建和测试时间,但同时也可能延长反馈循环。