中文翻译版:
为了使大家确信“应做单元测试,就一定能做单元测试”,谷歌测试工程师Mona El Mahdy专门写了一篇博客,提出了几种执行安卓应用用户界面单元测试的方法。Mahdy推荐使用 Robolectric和Android Studio Gradle插件做常规的单元测试,用 Espresso 或 UI Automator 创建和运行单元测试。
端到端的测试。Mahdy提出的第一种UI测试方法是E2E。这样的测试应该可以启动安卓应用及其所有与之相关的后端系统,使之可以在真实的场景中完成UI测试。重复执行这些的测试是很困难的,因为“存在很多的影响因素,比如网络带宽、实际服务器的认证、系统规模等”,所以就很难做到“E2E测试的调试排查和稳定化处理”。为简化这些点,Mahdy也提出了一些其他的测试策略。
用隔离的服务器测试。隔离的服务器是指与外界隔离的服务器,是在本地运行的一台单独的服务器,不连接网络。在运行的时间通过依赖注入(可能需要绑定到这台服务器的静态文件)的方式提供与其他服务器的连接,如果需要的话,还可以在本地文件或内存存储中伪造一些响应数据。另一个选择是,为隔离的服务器提供一系列记录好的响应,以模拟真实终端服务器的返回值。
针对测试目的,隔离的服务器作为被测系统在同一台机器上本地运行,其上运行安卓模拟器。虽然这种方式提高了测试的执行速度,有时也会消除一些网络连接的碎片,但是它需要单独的集成测试以确保客户端应用与后端是同步的。出于这个目的,Google+团队用了一组“黄金的”需求/响应文件,大家都知道它们包含了客户端应用到终端服务器的正确的传输序列。用黄金的需求文件根据这台服务器的所有响应生成一个文件,然后再用这个文件与黄金的响应文件进行比较。
Mahdy提出,使用隔离的服务器比E2E测试可以更好地完成测试,但是调试却仍旧是个难题,而且这台隔离的服务器可能会导致一些通讯碎片。所以,她提出了另一个改进方案。
使用依赖注入(DI)。移动应用可以设计成支持依赖注入的,在测试期间有些模块可以用仿造模块代替。应用将和仿造的网络模块进行通讯,这个网络模块为所有请求提供应答,以取代刚才所讲的使用网络模块的隔离的服务器。这会使UI测试更快更可靠。在DI这一方面,Mahdy建议使用Dagger。
多重类库。如果安卓应用比较大,Mahdy建议把它分割成更多小的组件,每个和它自己的模块和视图把包到一个单独的类库中。然后,你们就可以使用单独的DI、模拟模块和测试进行每个组件的开发和测试了。为确保整个应用的运转,需要集成的测试。这一方式更是进一步提升了测试的速度。
最后,Mahdy总结道:“组件化的UI测试要比E2E测试快得多,并具备99%的稳定性。快速而稳定的测试从根本上提升了开发人员的生产效率。”
英文原文版:
Convinced that “whatever can be unit tested should be unit tested”, Mona El Mahdy, a Software Engineer in Test at Google, has written a blog post proposing several approaches to perform unit tests on the user interface of Android applications. Mahdy recommends Robolectric and the Android Studio Gradle plug-in for general unit testing, and Espresso or UI Automator for creating and running UI tests.
End-to-End (E2E) Tests. The first approach to UI testing mentioned by Mahdy is E2E. Such a test should be able to fire up the Android application and all its related back-end systems, enabling the UI testing in real life scenarios. Repeatedly performing such tests is difficult because “debugging and stabilizing E2E tests” is hard due to “many variables like network flakiness, authentication against real servers, size of your system, etc.” To simplify things a bit, Mahdy proposes another strategy for testing.
Testing using Hermetic Servers. Hermetic servers are servers that are isolated from the outside world, running locally on a single machine without network connections. Connections to other servers are provided at runtime through dependency injection, any static files that might be requested are bundled with the server, and, if necessary, responses to data store requests are faked with data from local files or an in-memory store. Another option is for a hermetic server to provide a sequence of responses recorded when returned by a real back-end server.
For testing purposes, hermetic servers run locally on the same machine as the System Under Test (SUT) where the Android emulator runs. While this approach speeds up test runs and eliminates flakiness sometimes associated with network connections, it requires separate integration testing to make sure the client app is in sync with the back-end. For this purpose, the Google+ team uses a pair of “golden” request/response files that are known to contain a correct sequence of transmissions between a client app and a back-end server. The golden request file is played and a file is created with all the responses coming from the server, being later compared with the golden response file.
Mahdy argues that using hermetic servers is better for testing than E2E tests, but debugging is still not easy and the hermetic server may cause some flakiness in communication. So, she proposes another improvement.
Using Dependency Injection (DI). The mobile application can be designed for using DI having some modules replaced with faked ones during testing. Instead of talking to a hermetic server through a network module, the app will communicate with a fake network module which provides answers to all requests. This makes UI tests even faster and reliable. For DI, Mahdy suggests using Dagger.
Multiple Libraries. When an Android application is larger Mahdy suggest splitting it up into smaller components each with its own module and view packaged in a separate library. Then, each component can be developed and tested using separate DI, fake modules and tests. Integration tests are necessary to make sure the entire app works. This approach speeds up testing even further.
At the end, Mahdy concludes: “Componentized UI tests have proven to be much faster than E2E and 99%+ stable. Fast and stable tests have proven to drastically improve developer productivity.”