本文主要介绍如何通过instrumentation和其他测试工具对activity进行测试。
具体的测试代码正在整理,下月blog记录下
不同于其他组件,activity有一个复杂的基于回调方法的生命周期,除了instrumentation外不可显示被其他类调用,所以activity的测试尤其依赖Android instrumentation的框架。
1、测试activity的api
测试activity的api的基类是InstrumentationTestCase,这个基类为其提供了instrumentation。
1.1 对于activity测试,InstrumentationTestCase这个基类提供了如下功能:
a. 生命周期控制:通过instrumentation,你可以控制被测试activity的启动、暂停和销毁等。通常情况下,组件的生命周期是由系统控制的,例如大家熟悉的activity生命周期的相应函数onCreate、onResume、onStop等函数都是由系统自行调用,并且android应用框架并没有提供权限让用户直接调用,但是在instrumentation中却可以调用。
b. 依赖注入:你可以使用instrumentation mock系统对象,如contexts、applications,使用它们启动activity。使得被测点可以和数据分离。
c. 用户界面交互:你可以使用instrumentation向被测activity ui发送activity发送键盘、触摸、手势事件。
用于测试activity的两个主类是ActivityInstrumentationTestCase2
和 ActivityUnitTestCase。
为了测试非标准态的activity,你可以使用SingleLaunchActivityTestCase
。
1.2 ActivityInstrumentationTestCase2
适用于多个activity见的功能测试,以及activity对intents的响应测试。被测试activity通过系统基础api创建,你可以直接操作创建的activity。取代了之前的ActivityInstrumentationTestCase。
优点:
a. 可以方便的mock intents在各个activity中进行切换,测试activity对不同类型intents的响应,更多见setActivityIntent(Intent)
b. 可以在ui线程运行任何测试方法,更多见UiThreadTest
缺点:因为通过正常的系统基础api创建,不允许mock contexts或者applications,所以无法将activity和系统其他部分隔离进行独立测试。
适合于将单个activity从系统中隔离独立进行测试,以及activity中mock context和application的测试。被测试activity创建时尽可能少的依赖系统基础,你可以mock掉activity的诸多依赖。
优点:activity启动前可以方便的mock context、application等,使得activity的创建相对独立,从而进行不与系统交互的单元测试。
缺点:activity并非运行在通常的系统中,也无法与系统中的其他activity进行交互,所以很多函数无法正常调用或是调用后不做任何事情。
1.4 SingleLaunchActivityTestCase
适合对非标准态(singleTop、singleTask、singleInstance)的activity进行测试。它仅调用setUp()和tearDown()一次,而不是每个方法调用都会调用一次setUp和tearDown。所以可以在setUp中创建activity,在tearDown中销毁activity,这样在整个测试类执行过程中,activity只被创建一次。
如果大家了解activity启动的几种方式的话肯定会联想到singleTop、singleTask、singleInstance这三种启动模式,这个类实际上就是为了模拟对这三种启动模式的测试。关于这三种模式可以参考:http://trinea.iteye.com/blog/1112902
优点:可以对非标准模式的activity进行测试,能够保证在测试过程中配置不变从而测试activity对不同调用的处理。
缺点:无法mock任何对象。
2、测试重点
2.1 输入的有效性:这个跟很多软件和网页开发类似,就不用多说了,可以使用mokey和monkeyrunner进行自动化测试。
2.2 生命周期事件:重点在生命周期状态切换时是否正确保存当前状态并且响应。
2.3 Intents:activity能正确处理在AndroidManifest.xml文件中intent filter节点配置的intent,可以使用 ActivityInstrumentationTestCase2 mock不同的intents进行测试。
2.4 运行时配置变化:在程序运行时配置变化是否能够正确响应,如屏幕方向、修改语言等等。更多变化可以见 Handling Runtime Changes.
2.5 屏幕尺寸和分辨率:在应用发布前需要测试应用在不同屏幕尺寸和分辨率设备上的运行情况,可以直接通过修改avds的尺寸或是直接在目标设备上测试,更多可以参考Supporting Multiple Screens。
3、UI测试说明
应用的activities运行在UI线程即主线程内,一旦UI实例化后,所有跟UI的交互操作必须在这个主线程内完成,可以参考http://trinea.iteye.com/blog/1142151。当程序正常运行时,拥有对该线程的访问权限。
当进行测试时,基于instrumentation的类,可以调用方法进行UI操作,而其他的类不可以。为使某个测试方法在UI Thread中运行,可以使用@UIThreadTest对该方法进行标注,这样该方法的所有语句将运行在UI Thread中,并且不允许包含不与UI交互的语句,如Instrumentation.waitForIdleSync().
为了在UI Thread中运行测试方法的一个子集,可以创建Runnable的一个匿名子类,将运行的语句放在run()方法内,然后实例化一个对象作为appActivity.runOnUiThread() 参数。其中appActivity为被测activity的一个实例化对象。具体见示例代码。
下面是几个可能碰到的问题解决方法
1、为了使得设备响应测试发送过来的键盘事件,必须关闭touch模式,否则无法响应。
关闭touch模式方法:调用getActivity()方法之前调用ActivityInstrumentationTestCase2.setActivityTouchMode(false)
,并且只能在运行于非UI Thread的方法中调用。
2、当屏幕锁住时,测试无法运行。
解决方法:禁止屏幕锁屏
a. 在AndroidManifest.xml
文件的manifest节点下添加,添加
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
表示允许程序使用禁止屏幕锁屏权限。
b. 在被测试activities的onCreate()函数中添加
mKeyGuardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); mLock = mKeyGuardManager.newKeyguardLock("activity_classname"); mLock.disableKeyguard();
其中activity_classname为被测试activity的类名。
3、WrongThreadException
测试用例执行失败报如下异常:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
这个问题是因为在非UI线程内进行了UI的操作导致的,可以通过添加@UiThreadTest或者使用runOnUiThread()方法。
若开发代码中报,解决可参考:http://trinea.iteye.com/blog/1142151
4、java.lang.RuntimeException
测试用例执行失败报如下异常:
java.lang.RuntimeException: This method can not be called from the main application thread
这可能是在UI Thread(主线程)内进行了非主线程允许的操作,可能是方法添加了@UiThreadTest或者使用了runOnUiThread()方法。
参考:http://developer.android.com/guide/topics/testing/activity_testing.html
http://developer.android.com/resources/tutorials/testing/activity_test.html