android
测试框架是开发环境的一部分,它提供了 an architecture 以及帮助开发者测试应用中从单元到框架每个部分的有用工具。
该测试框架有如下的重要特征:
1、android测试套件基于JUnit。你可以用纯粹的JUnit来测试一个类,或者用android的JUnit扩展来测试android组件。如果你刚开始接触android测试,你可以从使用一般用途的AndroidTestCase类开始,然后再使用更复杂的类。
2、android的JUnit扩展提供了针对不同组件的测试类(test
case classes)。这些类提供了创建虚拟对象的方法以及帮助开发者控制组件生命周期的方法。
3、测试套件(test suites)都包含在类似于主应用程序包的测试包中,所以你不必重新学习一套设计和编译测试的工具和技术。
4、编译和测试的SDK工具在装有ADT的eclipse中可用,在使用其它IDE时也可以通过命令行的形式使用。这些工具从待测试应用的工程中取得信息并且自动为测试包创建编译文件、manifest文件以及文件目录。
5、SDK提供了monkeyrunner工具,它是一种用python来测试设备的API。SDK同时提供了UI/Application
Exerciser Monkey工具,它是一种通过发送伪随机事件给设备来对UI进行压力测试的命令行工具。
本文档描述了android测试框架的基础,包含测试代码的结构、用来开发测试的API以及开发者用来启动测试和查看测试结果的工具。本文档假设你有android应用编程以及JUnit测试的基础。
下面这张图简单描绘了andorid 的测试框架:
测试结构
android的编译和测试工具假设测试工程都已经组织成类似测试、测试类、测试包和测试工程这样的标准结构。
android测试基于JUnit。通常,一项JUnit测试就是一个用来测试“待测试应用某一部分”的方法。开发者将这些测试方法放入称为测试项
test cases (或者测试套件 test suites)的类中。每一项测试都是待测试应用中单独模块的一个独立测试,每个类是相近的测试方法的容器,通常它也提供额外的辅助方法。
在JUnit中,开发者编译一个或者多个测试源文件到一个class文件中。类似地,在android中开发者用android的编译工具编译测试包中一个或者多个测试源文件为class文件。在JUnit
中,开发者用 test runner 来执行测试类 (test classes)。在android中,开发者可以使用测试工具加载测试包和待测试应用程序,之后测试工具会调用android特有的
test runner。
测试工程
类似于android应用,测试是以工程的形式组织的。
一个测试工程就是一个目录或者eclipse 工程,开发者在其中新建源代码、manifest文件以及测试包的其它文件。android
SDK 为开发者提供了创建和更新测试工程的工具,包含命令行和带有ADT的eclipse 两种形式。工具可以帮助创建用来存放测试工程的源代码、资源文件和manifest文件的目录。命令行工具同时会创建ant编译文件。
开发者总应该用android工具来新建一个测试工程,工具可以做到:
自动帮助配置测试包使用InstrumentationTestRunner作为作为
test case runner,开发者必须使用InstrumentationTestRunner或者它的子类来执行JUnit
测试。
为测试包取一个合适的名字。如果待测试应用包名为com.mydomain.myapp,android
工具会自动命名测试包名为com.mydomain.myapp.test ,这可以帮助开发者识别它们之间的关系,同时也避免了冲突。
自动为测试工程创建合适的编译文件、manifest 文件以及目录结构。它帮助开发者在无需修改编译文件和配置测试包和待测试应用程序间关系的情况下编译测试包。
你可以再文件系统的任意地方创建一个测试工程,但是最好的做法是添加一个测试工程,这样测试工程的根目录
tests/ 和待测试应用的 src/目录处于同一级别,这将方便你找到与应用程序相关联的测试项。例如,如果你的应用程序工程的根目录是
MyProject ,那么你应该用如下的目录结构:
MyProject/ AndroidManifest.xml res/ ... (resources for main application) src/ ... (source code for main application) ... tests/ AndroidManifest.xml res/ ... (resources for tests) src/ ... (source code for tests)
|
测试API
android 测试API基于JUnit测试API 扩展了一个instrumentation
框架和android特有的测试类。
JUnit
你可以使用JUnit的TestCase类对未使用android API的类进行单元测试,TestCase也是AndroidTestCase的基类,你可以用它来测试依赖于android的对象。除了提供JUnit框架,AndroidTestCase提供了android特有的
setup、teardown以及其它的方法。
你可以使用JUnit的Assert类来显示结果,assert方法将你的期望值与实际结果比较,在比较失败时抛出异常。android同样提供了一个扩展了比较类型的断言类,以及另外一个用来测试UI的断言类,这些都在
Assertion classes中由详细的描述。
你可以阅读junit.org主页的文档来学习更多的JUnit内容。注意android测试API支持JUnit
3的代码风格,不支持JUnit 4。同时,你必须使用android的test runnerInstrumentationTestRunner来运行测试类。这个test
runner在Running Tests部分有介绍。
Instrumentation
android instrumentation是android系统中一系列的控制方法或者钩子(hooks)。这些hooks可以脱离组件的正常生命周期控制一个android组件。它同样可以控制android如何加载应用程序。
通常情况下,一个android组件会按照系统指定的生命周期来运行,例如,一个Activity的生命周期开始于它被一个Intent激活,它的onCreate()方法会被调用,接下来是onResume(),当用户启动另外一个应用程序,onPause()方法会被调用
,如果Activity调用finish()方法,它的onDestroy()方法也会被调用。android
framework API不会提供方法让你在代码中直接调用这些回调方法,但是你可以用instrumentation做到。
同时,系统将一个应用中的所有组件运行在同一个进程中,你可以让一些组件,比如content
provider,运行在一个单独的进程中。但是你无法强制让一个应用与另外一个正在运行的应用程序运行在同一个进程中。
通过android imstrumentation,你可以在测试代码中直接调用回调方法。它可以让你一步一步地运行一个组件的生命周期,就像你正在调试这个组件。下面的测试代码演示了如何用instrumentation来测试一个Activity保存和恢复状态:
// Start the main activity of the application under test
mActivity = getActivity();
// Get a handle to the Activity object's main UI widget, a Spinner
mSpinner = (Spinner)mActivity.findViewById(com.android.example.spinner.R.id.Spinner01);
// Set the Spinner to a known position
mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION);
// Stop the activity - The onDestroy() method should save the state of the Spinner
mActivity.finish();
// Re-start the Activity - the onResume() method should restore the state of the Spinner
mActivity = getActivity();
// Get the Spinner's current position
int currentPosition = mActivity.getSpinnerPosition();
// Assert that the current position is the same as the starting position
assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition); |
代码中使用的一个关键方法是getActivity(),它就是android
instrumentation API中的内容。直到你调用该方法,需要测试的Activity才会被启动。你可以提前配置测试所需的环境(test
fixture),然后再调用该方法启动Activity。
同时,instrumentation可以加载测试包和被测试应用程序到同一个进程中。因为应用程序的组件和测试都在同一个进程中,测试代码可以调用应用组件的方法,修改和检查组件中的属性。
Test case 类
android提供了几个继承自TestCase和Assert的test
case 类,它们都有andorid 特有的setup、teardown以及其它的辅助方法。
AndroidTestCase
一个通用的test case类,特别是你刚开始学习android测试,可以从AndroidTestCase开始。它继承了TestCase和Assert类。它提供了标准的JUnit中的setup()和teardown()方法,同时还有JUnit的所有Assert方法。另外,它也提供了用来测试权限的方法以及通过清除一定的类引用来防止内存泄露的方法。
组件特有的 test cases
android测试框架的一个重要特点即使组件独有的 test cases
类。这些特有的组件测试需要提供测试前配置、测试后回收的方法控制组件生命周期的方法。同时它们 也提供创建模拟对象的方法。这些类都会在组件特有测试的内容中讲述:
1、Activity Testing
2、Content Provider Testing
3、Service Testing
android并没有为 BroadcastReceiver 提供一个单独的
test case 类。可以通过测试发送Intent对象给它的组件来测试BroadcastReceiver,检查BroadcastReceiver回复是否正确。
ApplicationTestCase
你可以用ApplicationTestCase这个 test case
类来测试Application对象的启动和退出。这些对象维护着应用程序包中所有组件信息的全局状态,这个test
case 用于验证manifest 文件中的<application>元素是否正确配置。然而记住这个test
case 无法控制应用包组件的测试。
InstrumentationTestCase
如果你想在一个test case 类中使用 instrumentation
的方法,你必须使用InstrumentationTestCase或者它的子类。Activity 的test
case 继承该基类,同时扩展了一些辅助Activity 测试的功能。
Assertion 类
因为android test case 类继承自 JUnit,你可以用断言来显示测试结果。assertion
方法将测试返回的真实值和期望值进行比较,如果比较失败它会抛出一个AssertionException。用Assertion比打印log
更方便,而且提供更好的测试性能。
除了JUnit的Assert类的方法,测试API同时也提供了MoreAsserts和ViewAsserts类:
1、MoreAsserts包含更多功能强大的断言,例如进行正则表达式匹配的assertContainsRegex(String,
String)。
2、ViewAsserts包含很多关于View的断言,例如它包含用来测试一个View是否在屏幕的特定的(X,Y)位置的assertHasScreenCoordinates(View,
View, int, int)方法,这些断言简化了UI中的结合和对准测试。
模拟对象类
为了解决测试过程中的依赖,android提供了用来创建模拟的系统对象的类,比如Context对象、ContentProvider对象、ContentResolver对象以及Service对象。有些
test case也提供模拟的Intent对象。通过使用这些模拟对象,你可以将测试与系统的其余部分隔离开,同时也满足了测试中的依赖,这些类都在包android.test和android.test.mock中。
模拟对象通过不使用或者覆写正常操作来实现将测试与正在运行的系统隔离。例如,MockContentResolver对象用它自有的与系统隔离的本地框架来代替通常的resolver
框架。同时MockContentResolver不使用notifyChange(Uri, ContentObserver,
boolean)方法,这样测试环境以外的observer对象不会被异常触发。
模拟对象类通过提供正常类的子类来满足测试的依赖,该子类除了你覆写的方法外其它都是不起作用的。例如,MockResources对象提供了Resources类的一个子类,其中每个方法在调用时都会抛出异常。要使用它,你只需要覆写需要的方法。
下面是android中可用的模拟对象类:
基本的模拟对象类
MockApplication、MockContext、MockContentProvider、MockCursor,、MockDialogInterface、MockPackageManager和MockResources提供了一种简单有用的模拟策略。它们都是系统中对应的类的“方法不可用”的版本,它们的所有方法在调用时都会抛出UnsupportedOperationException异常。要使用它们,你需要覆写用来满足依赖所需要的方法。
注意:MockContentProvider和MockCursor是API
Level 8 中新加入的API。
Resolver 模拟对象
MockContentResolver通过屏蔽系统正常的resolver框架来为content
provider 提供隔离的测试。MockContentResolver不是在系统中查找提供authority的content
provider,而是使用它自己的内部表,你必须显式地用addProvider(String, ContentProvider)方法将provider添加到表中。
通过这个特性,你可以将一个模拟的content provider与一个authority关联,新建一个provider对象使用测试数据,你甚至可以设置provider的authority为null。事实上,MockContentResolver对象将你的测试与包含真实数据的provider隔离。你可以控制provider的功能,也可以防止测试影响真实数据。
用来测试的Context
android提供了两个Context类来提供测试:
1、IsolatedContext类提供一个隔离的Context,使用该Context的文件、目录和数据库操作都会在一个单独的测试区域。尽管功能有限,该类足以应对系统调用(this
Context has enough stub code to respond to system calls)。这个类允许你在不影响当前设备上的真实数据的前提下测试应用的数据操作。
2、RenamingDelegatingContext提供了这样一个Context,它的大部分功能都由一个现存的Context来处理,但是文件和数据库操作都由一个IsolatedContext来处理,隔离的部分使用一个测试目录,并且创建特殊的文件和目录名,你可以自己控制命名,也可以让constructor自动指定。该类为进行数据操作创建一个隔离区域提供了快捷办法,同时不会影响Context其它的正常操作。
运行测试
test case 都是由一个test runner 类来运行,test
runner 加载 test case 类、初始化、运行及清理每一项测试。android test runner
必须被注册(must be instrumented),这样启动应用的系统功能可以控制测试包是如何加载test
case和被测试包的。你可以通过在测试包的manifest文件中设定一个值来告诉系统使用哪个注册了的test
runner。
InstrumentationTestRunner是android中主要的test
runner类,它扩展了JUnit test runner框架并且是已经注册的。它能够执行所有由android系统提供的test
case 类并且支持所有类型的测试。
你可以指定测试包的manifest文件的<instrumentation>标签内容为Instrumentation
或者它的子类。InstrumentationTestRunner的代码在共享库android.test.runner中,所以它通常没有链接到你的代码,你必须在一个<uses-library>标签中指定它才可以。你不知道自己手动去设定这些标签,带有ADT的eclipse以及android
命令行工具都会自动生成它们并且把它们加到测试包的manifest文件中。
注意:如果你使用的是InstrumentationTestRunner之外的test
runner,你必须手动修改<instrumentation>标签并指向你想使用的类。
要运行InstrumentationTestRunner类必须用android
工具调用内部隐藏的系统类。当你用带有ADT的eclipse执行测试的时候,这些类都会被自动调用,当你用命令行工具执行测试的时候,可以用Android
Debug Bridge (adb)运行这些类。
系统类加载和启动一个测试包,杀掉被测试应用包正在运行的进程,并且重新加载一个被测试包的实体,然后它们把控制权交给InstrumentationTestRunner,由它来执行测试包中的每个test
case。你也可以通过eclipse中的setting或者命令行工具中的flag来控制哪些 test case或者方法在运行。
既不是系统类也不是InstrumentationTestRunner运行被测试包,这是由test
case来做的。它要么调用被测试包中的方法,要么调用它自己的可以改变被测试包生命周期的方法。应用程序完全由test
case 控制,在一项测试开始前由test case来初始化测试环境,这在前面的测试展示一个Spinner的Activity的code
snippet中有演示。
关于更多的运行测试,可以参见 Testing from Eclipse
with ADT和 Testing from Other IDEs。
查看测试结果
android 测试框架返回测试结果给启动测试的工具。如果你是在带有ADT的eclipse中运行测试,结果会在一个新的JUnit视图面板中显示,如果你从命令行启动测试,结果会在STDOUT中显示。在任何一种情况下,你都可以看到一份显示每个test
case 名字和你所运行的方法的简要总结,你同时会看到所有失败的断言,其中包含指向产生失败的测试代码所在行的链接。失败的断言同时也会列出期望值和实际值。
测试结果根据你所使用的IDE不同而有不同的格式。带有ADT的eclipse的测试结果格式在
Testing from Eclipse with ADT中有描述,从命令行中开始运行的测试结果格式在Testing
from Other IDEs部分有描述。
monkey 和 monkeyrunner
SDK提供了两个非常实用的应用测试工具:
UI/Application Exerciser Monkey,通常称为"monkey",它是一个向设备发送伪随机事件流(如击键、触控、手势)的命令行工具。你可以通过Android
Debug Bridge (adb)来运行它,对应用程序进行压力测试然后报告遇到的错误。你可以通过每次使用相同的随机数种子来运行它以重复事件流。
monkeyrunner是一套API,也是用Python编写的测试程序的运行环境。该API包含如下功能:连接到一台设备、安装和卸载软件包、截图、比较两张图片(comparing
two images)、运行应用程序对应的测试应用。通过该API,你可以写出功能强大复杂的测试。使用该API
的程序可以通过命令行工具monkeyrunner来运行。
处理包名
在整个测试环境中,你需要同时处理android应用包名和java 包标示符。它们都使用同样的命名格式,但是代表着完全不同的实体,为了正确启动测试你需要知道它们之间的区别。
android包名是.apk文件对应的一个独一无二的系统名字,由应用包的manifest文件中<manifest>标签中的"android:package"属性来设定。测试包的名字必须和被测试包的名字不同,通常android工具会用被测试包的名字后加上".test"来作为测试包的名字。
测试包也会用包名来定位它所测试的应用,由测试包的manifest文件中<instrumentation>元素的"android:targetPackage"属性设定。
一个java包标示符对应一个源文件,包名反映了源文件所在目录,它同时会影响类与成员间彼此的可访问性。
创建测试项目的android 工具会帮助你设定一个测试包的名字。根据你的输入,工具会设定测试包的名字以及测试的目标包的名字。只有在被测试应用工程已经存在的情况下这些工具才会起作用。
默认情况下,这些工具会将测试类的包标示符设定为与被测试应用的包标示符一致。如果你想暴露被测试包中的一些成员你可能需要做一些修改。如果要修改,只修改java
包标示符,不要修改android 包名,只修改test case 的源文件而不要修改测试包中R.java文件的包名,因为修改它会造成与被测试包中的R.java类冲突。不要将测试包的android包名修改成和它所测试的应用的包名一样,因为这样它们的名字在系统中不再是独一无二的。
测试什么
What To Test详细地描述了一个android应用中应该被测试的关键功能以及可能会影响该功能的状况。
大部分的单元测试是专门针对你正在测试的andorid组件。Activity
Testing、 Content Provider Testing和 Service Testing中都有一章节列出“需要测试什么”。
可以的话,你应该在一台真实的设备上运行这些测试。不行的话,你可以使用Android
Emulator来加载已经配置好你所希望测试的硬件、屏幕、版本的android vitual device。
|