自动化测试可通过多种方式帮助您提高应用质量。例如,它可以帮助您执行验证、捕获回归问题和验证兼容性。借助良好的测试策略,您可以充分利用自动化测试,专注于一项重要优势:开发者效率。
如果团队采用系统化的测试方法并搭配基础设施增强功能,就能实现更高的工作效率。这样做可以及时获得有关代码行为的反馈。良好的测试策略应做到以下几点:
- 尽早发现问题。
 - 执行速度快。
 - 在需要修正某些内容时提供清晰的指示。
 
本页面将帮助您确定要实施的测试类型、测试运行位置和测试运行频率。
测试金字塔
您可以按大小对现代应用中的测试进行分类。小型测试仅侧重于一小部分代码,因此速度快且可靠。大型测试的范围很广,需要进行更复杂的设置,因此很难维护。 不过,大型测试的保真度更高,可以一次性发现更多问题。
*保真度是指测试运行时环境与生产环境的相似程度。
  大多数应用都应包含许多小型测试,而大型测试相对较少。每类测试的分布应形成一个金字塔,其中数量较多的小型测试构成底部,数量较少的大型测试构成顶部。
尽可能降低 bug 的成本
良好的测试策略可最大限度地提高开发者生产力,同时最大限度地降低发现 bug 的成本。
不妨考虑一个可能低效的策略示例。在此示例中,按大小划分的测试数量并未形成金字塔。端到端测试过多,组件界面测试过少:
  这意味着,在合并之前运行的测试太少。如果存在 bug,测试可能要等到每晚或每周运行端到端测试时才能发现。
请务必考虑这会对发现和修复 bug 的成本产生什么影响,以及为什么应将测试精力侧重于规模较小且更频繁的测试:
- 如果通过单元测试发现 bug,通常可以在几分钟内修复,因此成本较低。
 - 端到端测试可能需要几天时间才能发现同一 bug。这可能会牵扯到多位团队成员,从而降低整体工作效率,并可能导致发布延迟。此 bug 的成本较高。
 
不过,低效的测试策略总比没有策略要好。如果 bug 进入生产环境,修复需要很长时间才能到达用户设备,有时甚至需要数周,因此反馈环是最长且成本最高的。
可扩展的测试策略
测试金字塔传统上分为 3 类:
- 单元测试
 - 集成测试
 - 端到端测试。
 
不过,这些概念没有明确的定义,因此团队可能希望以不同的方式定义其类别,例如使用 5 个层级:
  - 单元测试在主机上运行,用于验证单个逻辑功能单元,且不依赖于 Android 框架。
- 示例:验证数学函数中的差一错误。
 
 - 组件测试用于验证模块或组件的功能或外观,而无需考虑系统中的其他组件。与单元测试不同,组件测试的覆盖范围扩展到了高于单个方法和类的更高级别的抽象。
- 示例:针对自定义按钮的屏幕截图测试
 
 - 功能测试用于验证两个或更多个独立组件或模块的互动。功能测试规模更大、更复杂,通常在功能级别运行。
- 示例:验证屏幕中的状态管理的 界面行为测试
 
 - 应用测试以可部署二进制文件的形式验证整个应用的功能。它们是大型集成测试,使用可调试的二进制文件(例如可包含测试钩子的开发 build)作为受测系统。
- 示例:用于验证可折叠设备中的配置更改的界面行为测试、本地化测试和无障碍功能测试
 
 - 候选版本测试用于验证发布 build 的功能。
它们与应用测试类似,不同之处在于应用二进制文件经过了缩减和优化。这些是大型端到端集成测试,在尽可能接近生产的环境中运行,但不会将应用暴露给公共用户账号或公共后端。
- 示例:关键用户历程、性能测试
 
 
此分类考虑了保真度、时间、范围和隔离级别。您可以在多个层中进行不同类型的测试。例如,应用测试层可以包含行为测试、屏幕截图测试和性能测试。
范围  | 
    网络访问  | 
    执行  | 
    build 类型  | 
    生命周期  | 
  |
|---|---|---|---|---|---|
单位  | 
    依赖项最少的单个方法或类。  | 
    否  | 
    本地  | 
    可调试  | 
    合并前  | 
  
组件  | 
    模块级或组件级 多个类一起  | 
    否  | 
    本地  | 
    可调试  | 
    合并前  | 
  
功能  | 
    功能级 与其他团队拥有的组件集成  | 
    模拟  | 
    本地  | 
    可调试  | 
    合并前  | 
  
申请  | 
    应用级别 与其他团队拥有的功能和/或服务集成  | 
    模拟  | 
    模拟器  | 
    可调试  | 
    合并前  | 
  
候选版本  | 
    应用级别 与其他团队拥有的功能和/或服务集成  | 
    生产服务器  | 
    模拟器  | 
    精简版发布 build  | 
    合并后  | 
  
确定测试类别
一般来说,您应考虑金字塔中可为团队提供适当反馈的最低层级。
例如,考虑如何测试此功能的实现:登录流程的界面。您需要根据要测试的内容选择不同的类别:
受测对象  | 
    测试内容的说明  | 
    测试类别  | 
    测试类型示例  | 
  
|---|---|---|---|
表单验证器逻辑  | 
    一个类,用于根据正则表达式验证电子邮件地址,并检查是否已输入密码字段。它没有依赖项。  | 
    单元测试  | 
    |
登录表单界面行为  | 
    包含按钮的表单,该按钮仅在表单通过验证后启用  | 
    组件测试  | 
    在 Robolectric 上运行的 界面行为测试  | 
  
登录表单界面外观  | 
    遵循用户体验规范的表单  | 
    组件测试  | 
    |
与身份验证管理器集成  | 
    向身份验证管理器发送凭据并接收可能包含不同错误的响应的界面。  | 
    功能测试  | 
    |
登录对话框  | 
    按下登录按钮时显示的登录表单界面。  | 
    应用测试  | 
    在 Robolectric 上运行的 界面行为测试  | 
  
关键用户历程:登录  | 
    使用测试账号针对临时服务器的完整登录流程  | 
    候选版  | 
    在设备上运行的端到端 Compose 界面行为测试  | 
  
在某些情况下,某项内容属于哪个类别可能具有主观性。测试的优先级可能会因其他原因而提高或降低,例如基础架构成本、不稳定性以及测试时间过长。
请注意,测试类别并不决定测试类型,并且并非所有功能都必须在每个类别中进行测试。
手动测试也可以成为测试策略的一部分。通常,质量检查团队会执行候选版本测试,但他们也可以参与其他阶段。例如,在没有脚本的情况下对某项功能进行探索性测试,以查找 bug。
测试基础架构
测试策略必须有基础架构和工具的支持,以帮助开发者持续运行测试并强制执行可保证所有测试都通过的规则。
您可以按范围对测试进行分类,以定义何时何地运行哪些测试。例如,按照 5 层模型:
类别  | 
    环境(地点)  | 
    触发条件(何时)  | 
  
|---|---|---|
单位  | 
    [本地][4]  | 
    每次提交  | 
  
组件  | 
    本地  | 
    每次提交  | 
  
功能  | 
    本地和模拟器  | 
    合并前,在合并或提交更改之前  | 
  
申请  | 
    本地、模拟器、1 部手机、1 部可折叠设备  | 
    合并后,在合并或提交更改后  | 
  
候选版本  | 
    8 部不同的手机、1 部可折叠设备、1 部平板电脑  | 
    发行前  | 
  
- 单元和组件测试会在持续集成系统上针对每个新提交运行,但仅针对受影响的模块。
 - 所有单元、组件和功能测试都会在合并或提交更改之前运行。
 - 应用测试在合并后运行。
 - 候选版本测试每天晚上在手机、可折叠设备和平板电脑上运行。
 - 在发布之前,候选版本测试会在大量设备上运行。
 
当测试数量影响工作效率时,这些规则可能会随时间而变化。 例如,如果您将测试移至每晚进行,则可能会缩短 CI 构建和测试时间,但也可能会延长反馈环。