Android测试初探(一) Android测试技术简介和原理浅析

本系列文章共六篇,如下。

《Android测试初探(一) Android测试技术简介和原理浅析》
http://www.paincker.com/android-test-1

《Android测试初探(二) 单元测试实例:基于Instrumentation Test Runner的JUnit3测试》
http://www.paincker.com/android-test-2

《Android测试初探(三) 单元测试实例:基本的Android代码测试》
http://www.paincker.com/android-test-3

《Android测试初探(四) Android JUnit Test Runner与Espresso框架》
http://www.paincker.com/android-test-4

《Android测试初探(五) Rototium框架的使用、黑盒与白盒测试》
http://www.paincker.com/android-test-5

《Android测试初探(六) 利用UI Automator框架实现跨应用测试》
http://www.paincker.com/android-test-6

说明

文章主要介绍Android测试的基本概念、作用、特点,简单分析其原理,重点则介绍环境配置,不涉及深入的测试代码的编写等内容。如果读者发现文中有理解不准确和错误的地方,希望能及时指出。

文中介绍的Android测试技术主要包括单元测试和自动化测试,这两个概念有很多共同之处,但侧重点不同,单元测试侧重于对软件按单元模块分别进行测试,自动化测试则侧重于自动测试应用,取代重复的人工操作,提高测试效率。文中不对这样的概念做明确的区分。

环境配置

初学Android测试,遇到了很多环境配置的问题,前前后后看了很多资料研究了一周,终于基本搞清了。网上各种资料,包括Google官方的资料、翻译的国外文章、StackOverflow等网站的讨论、CSDN等各种博客,在环境配置上说法各异。有的说根本不用改gradle文件,有的说要改,方法也不一样。试了很多方法,但是一直遇到gradle同步下载jar包时提示连接失败的问题(应该是被墙了),尝试使用下载的jar包、修改repositories配置等方式,终于成功了。如果配置gradle时有问题,可以参考《gradle不能下载jar包的解决思路》。

网上之所以说法各异,大概是因为使用的Android Studio和gradle及插件版本不一样。本文使用的是Android Studio 1.2.2 + gradle 2.2.1 + android gradle plugin 1.2.3 环境,其他版本环境操作时可能有少量区别。另外,根据官网的资料,Android Gradle Plugin 1.1开始支持测试。

http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Testing

我遇到的环境配置问题大概分为三类:

  1. Gradle不能下载jar包,通过修改repository可以解决。具体参考:《Gradle不能下载jar包的解决思路》http://www.paincker.com/gradle-download-jar-file

  2. Gradle依赖项冲突或不兼容,尤其是Espresso配置时很容易出现。具体参考:《Gradle依赖项学习总结,dependencies、transitive、force、exclude的使用与依赖冲突解决》 http://www.paincker.com/gradle-dependencies

  3. Build Variant、Test Artifact参数设置,工程文件结构,测试代码的格式,导致各种奇怪的问题。例如常见的报错“Empty Test Suite”,可参考:《Android单元测试“Unable to find instrumentation info for: ComponentInfo{} Empty Test Suite”的解决思路》http://www.paincker.com/android-empty-test-suite

Android测试对于开发者的作用

Android测试可以通过代码直接测试项目中的指定类、方法,或是针对Android UI交互功能的测试等。一些大型和成熟的项目,一般都会用到测试,确保代码可靠性,例如谷歌官方推出的很多APP。

事实在于,很多时候不是非常成熟的项目开发时,都不会对代码去做全面的测试,当程序员忙得连需求都做不完的时候,根本没有多余的人力来写单元测试用例。

尽管如此,如果开发者对基本的测试技术有所了解,开发过程中还是可以充分利用其优势来测试代码,往往比常规的测试方法要简单有效的多。

而不管是做需求,还是修复BUG,或是给别人演示APP执行效果,测试代码一旦写好,就可以反复利用,节省了很多时间。

例一

例如我给APP写了一套自定义的弹窗,类似安卓自带的AlertDialog.Builder,想要测试这个工具类是否工作正常。实际项目中,调用弹窗的地方有很多,而有些样式的弹窗,可能会在很特殊的条件下才会显示。

用常规的做法,每次改了一行代码,我可能都需要像一个普通用户一样,在APP上做很多操作,甚至还需要服务器端、运营相关的人员协助,才能看到这个弹窗的展示效果。

显然我不打算这么做。之前我的做法是把所有弹窗相关的代码抽出来,放到另一个独立的工程中,然后另外写一些代码去调用相应的方法来测试。改完了BUG,还得重新放到工程中。对于一个稍微庞大点的项目,很多代码之间互相依赖,这样做无疑会浪费很多精力,还很容易出错。

但是有了Android测试,我可以直接在这个工程中添加测试代码,随意调用弹窗的方法,测试其可用性。测试完成后,什么都不用改,正式编译的过程中,测试代码不会被编译进去,没有任何影响。

例二

不少应用都会用到消息推送,比较常见的一种方法是Android后台Service和服务器端建立HTTP长连接,接收到数据通过广播发送给APP。有时客户端需要测试与之相关的逻辑,这时如果是常规的测试方法,就需要通过服务器端配置给设备发送推送消息,可能需要服务器端协调相关工作,相对比较繁琐。

如果用Android测试技术,可以用简单的几行代码,直接构建指定格式的Intent,通过Context发送广播,在APP收到广播后即可进行后续测试。

创建工程

这里要介绍的测试有两种,一种是基于Java的JUnit测试,一种则是基于Android的Instrumentation Test。前者可用于测试纯Java代码,后者则可以针对Android代码进行测试。

使用版本足够高的Android Studio环境(例如1.2.2),创建全新的Android工程时,测试环境就默认配置好了,甚至还创建了一个能直接运行的测试类。一切顺利的情况下,完全不需要修改任何配置,也不用改gradle文件,就能直接运行基于JUnit3的测试和Android Instrumentation Test。

Project窗口与工程文件结构

在支持测试的环境下,Android Studio创建完工程,默认会使用Android Instrumentation Test。

将Project窗口切换到Project视图,可以看到在app模块的src目录下,除了默认的源码目录main/java,还会自动创建一个测试文件夹androidTest/java,这两个java文件夹分别被标记为蓝色和绿色。在测试文件夹下还生成了一个ApplicationTest示例测试类,后面我们会创建自己的测试类。

如果没有测试文件夹,可以手动创建。

Test Artifact、Build Type与Build Variant

打开Build Variants窗口,可以看到里面有Test Artifact和模块的Build Variant选项。

Test Artifact有两种,Android Instrumentation Tests和Unit Tests,默认选中的是前者。在本文中,我们也始终使用前者。

Unit Tests是基于JVM的,只支持JUnit测试,其测试代码直接在电脑上的JVM中运行。测试类应该放在src/test/java目录下。

而Android Instrumentation Tests不仅支持JUnit测试,也支持Android测试,且测试代码均会在Android设备上运行。测试代码放在src/androidTest/java目录下。

由于Java是跨平台的,所以理论上JUnit测试在哪个设备上运行的结果都是一致的。

一个Android模块默认有两种buildType,debug和release,同时gradle允许给项目配置渠道即Flavor参数。窗口中模块app的Build Variant则为Flavor+BuildType的所有组合,默认没有Flavor所以就只有debug和release两种,如果配置了Flavor,则类似baiduDebug,baiduRelease,huaweiDebug,huaweiRelease。

需要注意的事,Android测试一定要选择*Debug类型的Build Variant,否则不能运行测试,会报错Empty Test Suits,这个问题我找了快一周才终于搞清楚。

安卓测试原理

Test Runner

测试时,需要有一个Test Runner可执行文件来运行测试代码。具体而言,对于Android测试来说,Runner本质上是一个没有界面的APK应用,通过一定的权限,Test Runner可以直接调用被测试APK的类、方法、资源,以及向其发送用户事件。

Activity、Resource资源的跨APP访问

在Android系统中,只要有一定的权限,不同应用之间可以通过隐式Intent互相调用Activity等组件,跨进程访问资源,例如一些应用的分享功能就是通过Intent调用了QQ、微信的Activity。

常规应用通过Intent Filter配置Activity才会支持外部调用,而在一些情况下,例如两个APK的签名一致,或是Manifest中的android:sharedUserId属性一致,则两者可以直接访问对方的资源,即使Activity并没有申明被外部调用。甚至还可以通过反射的方式直接调用类和方法。

Instrumentation

Instrumentation是Android Java API层的一个类,通过阅读源码可以发现,平时我们在用Application、Activity的时候,实际上Application的创建、Activity的实例化、生命周期里onCreate等方法的调用,都是通过Instrumentation实现的。不仅如此,通过Instrumentation还可以向APP发送用户点击、按钮事件等。获取到了权限足够的Instrumentation,几乎就可以完全控制APP甚至手机了。

对于一个APP,可以实例化Instrumentation对象,操作APP自身,包括发送用户事件、创建Activity等;
这个APP还可以通过Instrumentation操作与其签名相同的APP,从而可以作为Runner实现自动化测试;
如果这个APP的Manifest配置了android:sharedUserId="android.uid.system"权限,并加上系统签名安装到Android设备上,或是利用Android 4.3中新的Instrumentation相关API,则可以操作Android设备,从而进行跨应用的自动化测试。

Android Test Runner

Android Test Runner就是基于前面的原理实现的。

测试可分为白盒测试和黑盒测试。

在Android白盒测试时,被测试程序对于测试者而言是透明的,简单理解,就是其源码是可获取的。测试代码和程序源码一般在同一个工程中,测试代码可以直接访问程序源码中的方法,编译时由编译器自动处理,生成Test Runner和被测试APK,两者签名一致。Test Runner运行时,就可以对被测试APK进行指定的操作了。

而Android黑盒测试时,没有被测试程序源码,只有编译后的APK。此时可以新建独立的Android工程,使用反射的方式调用待测APP的相应类和资源id,只要测试代码编译后签名和被测APK一致,即可进行测试。