这是在 google testing blog上的一篇博文,主要讲述 Android UI 自动化测试。看完后觉得里面的很多东西很实用,所以翻译记录一下。
里面我不是很确定翻译是否正确的地方都附上了英文原文。大家如果发现有翻译错误的地方麻烦提醒一下,我马上更正。

概述

这篇博文回顾了关注于快速、可靠、便于调试的 Android UI 测试的4种策略。

在我们开始之前,请不要忘记一个首要原则(import rule):可以用单元测试完成的工作应该使用单元测试完成。 Robolectric gradle unit tests support 是用于 Android 的非常好的单元测试框架范例。从另一个角度来说, UI 测试是用来检查你的应用是否能对用户在设备上一系列操作进行正确的 UI 反馈。 Expresso 是一个良好的、用于在同一进程中运行 UI 动作及校验的框架。如果想了解关于 Espresso 和 UI 自动化工具,请查看 test support libraries

Google+ 团队对 UI 测试进行了很多次尝试。后面我们会讨论在 UI 测试中采用各种策略后学到的东西。想知道更多的细节或者代码示例,请保持关注。

策略1:使用端到端(End-To-End)测试的方法来做 UI 测试

我们先说明一些术语。 UI 测试用于检查你的应用是否能对用户在设备上一系列操作进行正确的 UI 反馈。 端到端(E2E)测试构建一个应用的完整的系统(包括后台服务器及客户端应用)。E2E 测试会检查数据是否被正确传送到客户端应用以及整个系统功能的正确性。

通常为了让应用 UI 功能能正常使用,你需要获得从后台服务器发出的数据,所以 UI 测试需要模拟这些数据(不一定通过后台服务器)。在多数情况下, UI 测试的概念会和 E2E 测试混淆,因为端到端测试和手工场景测试很相似。然而,调试和保持 E2E 测试的稳定性非常困难,因为存在太多环境因素了,例如网络碎片(network flakiness),真实服务器的认证服务,系统规模等。



android制作中文翻译器 android auto翻译_依赖注入



当你把 UI 测试像 E2E 测试那么进行,你会遇到下列问题:

  • 庞大且缓慢的测试
  • 由于超时和内存问题导致的高碎片率(flakiness rate,没找到合适的译名。大致意思就是用例不稳定,有时通过有时不通过,不通过的原因大多是环境造成的)
  • 难以调试/探索为何失败
  • 认证问题(例如在自动化测试中进行认证会非常需要技巧)

让我们看看怎么通过下面的策略来解决这些问题。

策略2:使用 Fake (伪造)服务器进行封闭的 UI 测试

在这个策略中,你避免了网络通讯及额外的依赖,但你需要提供具有能驱动 UI 的数据的应用。更新你的应用来和本地服务器——而不是外部服务器——进行通讯,并建立一个 fake 本地服务器来提供数据到你的应用。你需要一种能生成被测应用需要的数据的机制。取决于你的系统架构,这可以通过多种方法完成。其中一种就是记录服务器返回消息并在你的 fake 服务器中重播(replay)。

一旦你有了一个封闭的、与本地 fake 服务器通讯的 UI 测试环境,你同时应该有一些封闭的系统测试。通过这种方式,你把 E2E 测试分割成服务端测试和客户端测试,以及一个用于验证服务端和客户端同步的集成测试(更多关于集成测试的细节,请看博文的后端测试章节)

现在,客户端测试流程如下图:



android制作中文翻译器 android auto翻译_UI_02



虽然这种方法大幅度缩小了测试规模和碎片率(flakiness rate),你仍然需要像你的测试那样维护一个额外的 fake 服务器。由于你用两个不停变动的部分:测试用例及本地服务器,调试同样并不简单。虽然通过这种方法大幅度提高了测试稳定性,但本地服务器会造成一些碎片(flakes)。

让我们看看如何改善这点...

策略3:对应用设计进行依赖注入

为了去掉在 Android 上 fake 服务器这种额外的依赖,你应该对你的应用使用依赖注入来把一些真实模块的实现替换成伪造的(fake)。其中一个例子是 Dagger ,如果有需要,你也可以设计你自己的依赖注入机制。

这会提高你的应用在单元测试及 UI 测试中的可测性(testability),让你的测试能 mock (模拟)依赖。在 instrumentation 测试中,测试的apk和被测应用都加载在在同一进程中,所以测试代码能在运行时访问应用代码。不仅如此,你还可以使用 classpath 重载(实际上就是把测试的 classpath 优先级提高到比被测应用的高)来重载一个现有的类并注入测试代码。举个例子,为了让你的测试变得封闭(hermetic),你的应用应该支持网络实现的注入。在测试过程中,测试会注入一个 fake 的网络实现到应用中,而 fake 的实现会提供种子数据(seeded data)来替代后端服务器。



android制作中文翻译器 android auto翻译_UI_03



策略4:让应用通过使用多个小型库来进行构建(Building Apps into Smaller Libraries ,可以理解为模块化)

如果你希望把你的应用扩展到拥有大量的模块和视图,并计划在维护稳定版本的同时加入更多的特性并进行快速构建/测试,那你就需要把应用构建成多个小的组件/库。每个库应该有自己的 UI 资源和用户依赖管理(user dependency management)。这种策略不仅允许通过 mock 库的依赖来进行封闭测试,还能像一个对于应用多个组件进行测试的测试平台那样提供服务。

一旦你拥有支持依赖注入的小型组件,你可以为每个组件单独构建测试应用。

测试应用能带来你的库使用的实际 UI ,所需的 fake 数据,以及 moke 依赖。你能针对这些测试应用进行 Espresso 测试。这让测试小型且相互隔离的库成为可能。

举个例子,让我们看看构建用于应用的登陆和设置功能的小型库:



android制作中文翻译器 android auto翻译_依赖注入_04



设置组件的测试现在看起来是这样的:



android制作中文翻译器 android auto翻译_UI_05



总结

对于 Android 富功能应用(rich apps)进行 UI 测试会非常具有挑战性。以下是一些 Google+ 团队在 UI 测试中学到的经验总结:
1. 不要用 E2E 测试用来做 UI 测试。应该在 UI 测试之外编写单元测试和系统测试来替代这种方式
2. 封闭测试才是正确的道路
3. 在设计应用时使用依赖注入
4. 把你的应用设计成使用小型库/模块组成(即模块化),并在隔离的状态下分别测试他们。然后你就可以有一些集成测试来验证各模块之间的集成是否正确。
5. 组件式 UI 测试已经被证明比 E2E 测试更快速且稳定性在99%以上。快速和稳定的测试已被证明能大幅度提高开发产出率。