android源代码中每个app下中都自带了一个test用例,下面主要介绍下camra单元测试用例
在AndroidManifest.xml中标明了测试用例instrumentation函数入口
- <?xmlversion="1.0" encoding="utf-8" ?>
- <!--Copyright(C)2008 TheAndroidOpenSourceProject
- LicensedundertheApacheLicense,Version2.0 (the"License" );
- youmaynotusethis fileexceptincompliancewiththeLicense.
- YoumayobtainacopyoftheLicenseat
- http://www.apache.org/licenses/LICENSE-2.0
- Unlessrequiredbyapplicablelaworagreedtoinwriting,software
- distributedundertheLicenseisdistributedonan"ASIS" BASIS,
- WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
- SeetheLicensefor thespecificlanguagegoverningpermissionsand
- limitationsundertheLicense.
- -->
- <manifestxmlns:android="http://schemas.android.com/apk/res/android"
- package ="com.android.camera.tests" >
- <application>
- <uses-libraryandroid:name="android.test.runner" />
- </application>
- <instrumentationandroid:name="CameraLaunchPerformance"
- android:targetPackage="com.android.camera"
- android:label="CameraLaunchPerformance" >
- </instrumentation>
- <instrumentationandroid:name="com.android.camera.CameraStressTestRunner"
- android:targetPackage="com.android.camera"
- android:label="CameraStressTestInstrumentationRunner" >
- </instrumentation>
- <instrumentationandroid:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.camera"
- android:label="TestsforCameraapplication." />
- </manifest>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.camera.tests">
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="CameraLaunchPerformance"
android:targetPackage="com.android.camera"
android:label="Camera Launch Performance">
</instrumentation>
<instrumentation android:name="com.android.camera.CameraStressTestRunner"
android:targetPackage="com.android.camera"
android:label="Camera Stress Test InstrumentationRunner">
</instrumentation>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.camera"
android:label="Tests for Camera application."/>
</manifest>
camera启动性能测试
- package com.android.camera;
- import android.app.Activity;
- import android.os.Bundle;
- import android.test.LaunchPerformanceBase;
- /**
- *InstrumentationclassforCameralaunchperformancetesting.
- */
- public class CameraLaunchPerformanceextends LaunchPerformanceBase{
- public static final StringLOG_TAG="CameraLaunchPerformance" ;
- public CameraLaunchPerformance(){
- super ();
- }
- @Override
- public void onCreate(Bundlearguments){
- super .onCreate(arguments);
- mIntent.setClassName(getTargetContext(),"com.android.camera.Camera" );
- start();
- }
- /**
- *CallsLaunchAppandfinish.
- */
- @Override
- public void onStart(){
- super .onStart();
- LaunchApp();
- finish(Activity.RESULT_OK,mResults);
- }
- }
package com.android.camera;
import android.app.Activity;
import android.os.Bundle;
import android.test.LaunchPerformanceBase;
/**
* Instrumentation class for Camera launch performance testing.
*/
public class CameraLaunchPerformance extends LaunchPerformanceBase {
public static final String LOG_TAG = "CameraLaunchPerformance";
public CameraLaunchPerformance() {
super();
}
@Override
public void onCreate(Bundle arguments) {
super.onCreate(arguments);
mIntent.setClassName(getTargetContext(), "com.android.camera.Camera");
start();
}
/**
* Calls LaunchApp and finish.
*/
@Override
public void onStart() {
super.onStart();
LaunchApp();
finish(Activity.RESULT_OK, mResults);
}
}
camera拍照压力测试,参数设定为反复拍照100次
- package com.android.camera.stress;
- import com.android.camera.Camera;
- import android.app.Instrumentation;
- import android.test.ActivityInstrumentationTestCase2;
- import android.test.suitebuilder.annotation.LargeTest;
- import android.util.Log;
- import android.view.KeyEvent;
- /**
- *Junit/Instrumentationtestcaseforcameratest
- *
- *Runningthetestsuite:
- *
- *adbshellaminstrument\
- *-eclasscom.android.camera.stress.ImageCapture\
- *-wcom.android.camera.tests/com.android.camera.CameraStressTestRunner
- *
- */
- public class ImageCaptureextends ActivityInstrumentationTestCase2<Camera>{
- private StringTAG="ImageCapture" ;
- private static final int TOTAL_NUMBER_OF_IMAGECAPTURE=100 ;
- private static final int TOTAL_NUMBER_OF_VIDEOCAPTURE=100 ;
- private static final long WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN=1000 ;
- private static final long WAIT_FOR_VIDEO_CAPTURE_TO_BE_TAKEN=50000 ;//50seconds
- private static final long WAIT_FOR_PREVIEW=1000 ;//1seconds
- public ImageCapture(){
- super ("com.android.camera" ,Camera.class );
- }
- @Override
- protected void setUp()throws Exception{
- getActivity();
- super .setUp();
- }
- @Override
- protected void tearDown()throws Exception{
- super .tearDown();
- }
- @LargeTest
- public void testImageCapture(){
- Instrumentationinst=getInstrumentation();
- try {
- for (int i=0 ;i<TOTAL_NUMBER_OF_IMAGECAPTURE;i++){
- Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
- inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
- inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
- Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
- }
- }catch (Exceptione){
- Log.v(TAG,e.toString());
- }
- assertTrue("testImageCapture" ,true );
- }
- @LargeTest
- public void testVideoCapture(){
- Instrumentationinst=getInstrumentation();
- //Switchtothevideomode
- inst.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
- inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
- try {
- for (int i=0 ;i<TOTAL_NUMBER_OF_VIDEOCAPTURE;i++){
- Thread.sleep(WAIT_FOR_PREVIEW);
- inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
- //recordanvideo
- inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
- Thread.sleep(WAIT_FOR_VIDEO_CAPTURE_TO_BE_TAKEN);
- inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
- Thread.sleep(WAIT_FOR_PREVIEW);
- inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
- }
- }catch (Exceptione){
- Log.v(TAG,e.toString());
- }
- assertTrue("testVideoCapture" ,true );
- }
- }
package com.android.camera.stress;
import com.android.camera.Camera;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.view.KeyEvent;
/**
* Junit / Instrumentation test case for camera test
*
* Running the test suite:
*
* adb shell am instrument \
* -e class com.android.camera.stress.ImageCapture \
* -w com.android.camera.tests/com.android.camera.CameraStressTestRunner
*
*/
public class ImageCapture extends ActivityInstrumentationTestCase2 <Camera> {
private String TAG = "ImageCapture";
private static final int TOTAL_NUMBER_OF_IMAGECAPTURE = 100;
private static final int TOTAL_NUMBER_OF_VIDEOCAPTURE = 100;
private static final long WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN = 1000;
private static final long WAIT_FOR_VIDEO_CAPTURE_TO_BE_TAKEN = 50000; //50seconds
private static final long WAIT_FOR_PREVIEW = 1000; //1 seconds
public ImageCapture() {
super("com.android.camera", Camera.class);
}
@Override
protected void setUp() throws Exception {
getActivity();
super.setUp();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
}
@LargeTest
public void testImageCapture() {
Instrumentation inst = getInstrumentation();
try {
for (int i = 0; i < TOTAL_NUMBER_OF_IMAGECAPTURE; i++) {
Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
}
} catch (Exception e) {
Log.v(TAG, e.toString());
}
assertTrue("testImageCapture", true);
}
@LargeTest
public void testVideoCapture() {
Instrumentation inst = getInstrumentation();
//Switch to the video mode
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
try {
for (int i = 0; i < TOTAL_NUMBER_OF_VIDEOCAPTURE; i++) {
Thread.sleep(WAIT_FOR_PREVIEW);
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
//record an video
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(WAIT_FOR_VIDEO_CAPTURE_TO_BE_TAKEN);
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(WAIT_FOR_PREVIEW);
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
}
} catch (Exception e) {
Log.v(TAG, e.toString());
}
assertTrue("testVideoCapture", true);
}
}
camera拍照视频录制切换测试
- package com.android.camera.stress;
- import com.android.camera.VideoCamera;
- import android.app.Instrumentation;
- import android.test.ActivityInstrumentationTestCase2;
- import android.test.suitebuilder.annotation.LargeTest;
- import android.util.Log;
- import android.view.KeyEvent;
- /**
- *Junit/Instrumentationtestcaseforcameratest
- *
- *Runningthetestsuite:
- *
- *adbshellaminstrument\
- *-eclasscom.android.camera.stress.SwitchPreview\
- *-wcom.android.camera.tests/com.android.camera.CameraStressTestRunner
- *
- */
- public class SwitchPreviewextends ActivityInstrumentationTestCase2<VideoCamera>{
- private StringTAG="SwitchPreview" ;
- private static final int TOTAL_NUMBER_OF_SWITCHING=200 ;
- private static final long WAIT_FOR_PREVIEW=2000 ;
- public SwitchPreview(){
- super ("com.android.camera" ,VideoCamera.class );
- }
- @Override
- protected void setUp()throws Exception{
- getActivity();
- super .setUp();
- }
- @Override
- protected void tearDown()throws Exception{
- getActivity().finish();
- super .tearDown();
- }
- @LargeTest
- public void testSwitchMode(){
- //Switchingthevideoandthevideorecordermode
- Instrumentationinst=getInstrumentation();
- try {
- for (int i=0 ;i<TOTAL_NUMBER_OF_SWITCHING;i++){
- Thread.sleep(WAIT_FOR_PREVIEW);
- inst.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
- inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_LEFT);
- inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
- Thread.sleep(WAIT_FOR_PREVIEW);
- }
- }catch (Exceptione){
- Log.v(TAG,e.toString());
- }
- assertTrue("testSwitchMode" ,true );
- }
- }
package com.android.camera.stress;
import com.android.camera.VideoCamera;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.view.KeyEvent;
/**
* Junit / Instrumentation test case for camera test
*
* Running the test suite:
*
* adb shell am instrument \
* -e class com.android.camera.stress.SwitchPreview \
* -w com.android.camera.tests/com.android.camera.CameraStressTestRunner
*
*/
public class SwitchPreview extends ActivityInstrumentationTestCase2 <VideoCamera>{
private String TAG = "SwitchPreview";
private static final int TOTAL_NUMBER_OF_SWITCHING = 200;
private static final long WAIT_FOR_PREVIEW = 2000;
public SwitchPreview() {
super("com.android.camera", VideoCamera.class);
}
@Override
protected void setUp() throws Exception {
getActivity();
super.setUp();
}
@Override
protected void tearDown() throws Exception {
getActivity().finish();
super.tearDown();
}
@LargeTest
public void testSwitchMode() {
//Switching the video and the video recorder mode
Instrumentation inst = getInstrumentation();
try{
for (int i=0; i< TOTAL_NUMBER_OF_SWITCHING; i++) {
Thread.sleep(WAIT_FOR_PREVIEW);
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_LEFT);
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(WAIT_FOR_PREVIEW);
}
} catch (Exception e){
Log.v(TAG, e.toString());
}
assertTrue("testSwitchMode",true);
}
}
如果想在android里面做单元测试,有两条基本的路子可行。
第一, 就是java程序员最为熟悉和常用的JUnit, 但是由于目前android sdk (version 1.1)中只是提供了stubbed methods/classes,没有具体的实现代码,所以如果用JUnit的话,我们需要在运行单元测试时,一定要用JDK来运行,利用java命令来启动JUnit的某个Runner。如果是用Eclipse的话,可以在Run Configuration里新建一个JUnit。但是一定要记得在Classpath选项卡里将Bootstrap Entries中的Android Library改成JRE,并且添加junit.jar。具体的设置可以参考:http://developer.android.com/guide /appendix/faq/troubleshooting.html#addjunit。而且,更为遗憾的是,这种方法运行的JUnit运行在JDK 之上的,而不是android,所以,只能测试一些和android无关的东西,比如业务逻辑,数据封装,数值计算等等。并不能测试android api。
第二, 采用Instrumentation. Android单元测试的主入口是InstrumentationTestRunner。它相当于JUnit当中TestRunner的作用。你可以将 Instrumentation理解为一种没有图形界面的,具有启动能力的,用于监控其他类(用Target Package声明)的工具类。任何想成为Instrumentation的类必须继承android.app.Instrumentation。
下面通过一个实例来看一下如何通过Instrumentation来做单元测试。
Step 1.首先编写需要测试的activity:
- import android.app.Activity;
- import android.os.Bundle;
- public class AndroidUTextends Activity{
- /**Calledwhentheactivityisfirstcreated.*/
- @Override
- public void onCreate(BundlesavedInstanceState){
- super .onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- public int add(int a,int b)
- {
- return a+b;
- }
- }
import android.app.Activity;
import android.os.Bundle;
public class AndroidUT extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public int add(int a, int b)
{
return a + b;
}
}
Step 2.
接下来编写测试类,其中主要来测试add()方法。我们在当前代码目录下,在新建一个文件夹,命名为test,并在里面新建了包com.android.ut.test。然后往里面新增加一个class.具体如下:
- import com.android.ut.AndroidUT;
- import android.test.ActivityInstrumentationTestCase;
- public class TestAppextends ActivityInstrumentationTestCase<AndroidUT>{
- public TestApp()
- {
- super ("com.android.ut" ,AndroidUT.class );
- }
- public void testSum()
- {
- assertEquals(5 ,getActivity().add(2 ,3 ));
- }
- }
import com.android.ut.AndroidUT;
import android.test.ActivityInstrumentationTestCase;
public class TestApp extends ActivityInstrumentationTestCase<AndroidUT> {
public TestApp()
{
super("com.android.ut", AndroidUT.class);
}
public void testSum()
{
assertEquals(5, getActivity().add(2, 3));
}
}
Step 3.最后一步就是要改一下Manifest文件。
- <?xmlversion="1.0" encoding="utf-8" ?>
- <manifestxmlns:android="http://schemas.android.com/apk/res/android"
- package ="com.android.ut"
- android:versionCode="1"
- android:versionName="1.0.0" >
- <applicationandroid:icon="@drawable/icon" android:label="@string/app_name" >
- <activityandroid:name=".AndroidUT"
- android:label="@string/app_name" >
- <intent-filter>
- <actionandroid:name="android.intent.action.MAIN" />
- <categoryandroid:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <uses-libraryandroid:name="android.test.runner" />
- </application>
- <instrumentationandroid:targetPackage="com.android.ut" android:name="android.test.InstrumentationTestRunner" android:label="TestUnitTests" ></instrumentation>
- </manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.ut"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".AndroidUT"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:targetPackage="com.android.ut" android:name="android.test.InstrumentationTestRunner" android:label="Test Unit Tests"></instrumentation>
</manifest>
需要注意的是,在这里面我加上了:
- <uses-libraryandroid:name="android.test.runner" />
<uses-library android:name="android.test.runner" />
以及:
- <instrumentationandroid:targetPackage="com.android.ut" android:name="android.test.InstrumentationTestRunner" android:label="TestUnitTests" ></instrumentation>
<instrumentation android:targetPackage="com.android.ut" android:name="android.test.InstrumentationTestRunner" android:label="Test Unit Tests"></instrumentation>
Step 4.运行
首先通过模拟器运行一下AndroidUT,然后在命令行终端中运行
- adbshellaminstrument-eclass com.android.ut.test.TestApp-wcom.android.ut/android.test.InstrumentationTestRunner
adb shell am instrument -e class com.android.ut.test.TestApp -wcom.android.ut/android.test.InstrumentationTestRunner
这样你就可以看到测试结果了。
- #aminstrument-eclass com.cn.test.TestApp-wcom.cn/android.test.InstrumentationTestRunner
- com.cn.test.TestApp:..
- Testresultsfor InstrumentationTestRunner=..
- Time:2.866
- OK(2 tests)
# am instrument -e class com.cn.test.TestApp -w com.cn/android.test.InstrumentationTestRunner
com.cn.test.TestApp:..
Test results for InstrumentationTestRunner=..
Time: 2.866
OK (2 tests)
后台测试log日志信息
- D/AndroidRuntime(941 ):>>>>>>>>>>>>>>AndroidRuntimeSTART<<<<<<<<<<<<<<
- D/AndroidRuntime(941 ):CheckJNIisON
- D/AndroidRuntime(941 ):---registeringnative functions---
- D/FileBackupHelper_native(941 ):register_android_backup_FileBackupHelper
- D/ActivityManager(581 ):Uninstallingprocesscom.cn
- I/ActivityManager(581 ):Startproccom.cnfor addedapplicationcom.cn:pid=948 uid=10013 gids={}
- I/TestRunner(948 ):started:testSum(com.cn.test.TestApp)//启动add()测试方法
- I/ActivityManager(581 ):Startingactivity:Intent{act=android.intent.action.MAINflg=0x10000000 cmp=com.cn/.AndroidUT}
- I/ActivityManager(581 ):Displayedactivitycom.cn/.AndroidUT:645 ms(total645 ms)
- I/TestRunner(948 ):finished:testSum(com.cn.test.TestApp)
- I/TestRunner(948 ):passed:testSum(com.cn.test.TestApp)
- I/TestRunner(948 ):started:testActivityTestCaseSetUpProperly(com.cn.test.TestApp)
- I/ActivityManager(581 ):Startingactivity:Intent{act=android.intent.action.MAINflg=0x10000000 cmp=com.cn/.AndroidUT}
- I/ActivityManager(581 ):Displayedactivitycom.cn/.AndroidUT:412 ms(total412 ms)
- I/TestRunner(948 ):finished:testActivityTestCaseSetUpProperly(com.cn.test.TestApp)
- I/TestRunner(948 ):passed:testActivityTestCaseSetUpProperly(com.cn.test.TestApp)
- D/ActivityManager(581 ):Uninstallingprocesscom.cn
- D/ActivityManager(581 ):ForceremovingprocessProcessRecord{43851fa0948 :com.cn/10013 }(com.cn/10013 )
- D/AndroidRuntime(941 ):ShuttingdownVM
D/AndroidRuntime( 941): >>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<
D/AndroidRuntime( 941): CheckJNI is ON
D/AndroidRuntime( 941): --- registering native functions ---
D/FileBackupHelper_native( 941): register_android_backup_FileBackupHelper
D/ActivityManager( 581): Uninstalling process com.cn
I/ActivityManager( 581): Start proc com.cn for added application com.cn: pid=948 uid=10013 gids={}
I/TestRunner( 948): started: testSum(com.cn.test.TestApp) //启动add()测试方法
I/ActivityManager( 581): Starting activity: Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=com.cn/.AndroidUT }
I/ActivityManager( 581): Displayed activity com.cn/.AndroidUT: 645 ms (total 645 ms)
I/TestRunner( 948): finished: testSum(com.cn.test.TestApp)
I/TestRunner( 948): passed: testSum(com.cn.test.TestApp)
I/TestRunner( 948): started: testActivityTestCaseSetUpProperly(com.cn.test.TestApp)
I/ActivityManager( 581): Starting activity: Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=com.cn/.AndroidUT }
I/ActivityManager( 581): Displayed activity com.cn/.AndroidUT: 412 ms (total 412 ms)
I/TestRunner( 948): finished: testActivityTestCaseSetUpProperly(com.cn.test.TestApp)
I/TestRunner( 948): passed: testActivityTestCaseSetUpProperly(com.cn.test.TestApp)
D/ActivityManager( 581): Uninstalling process com.cn
D/ActivityManager( 581): Force removing process ProcessRecord{43851fa0 948:com.cn/10013} (com.cn/10013)
D/AndroidRuntime( 941): Shutting down VM
(二) 转
任何程序的开发都离不开单元测试来保证其健壮和稳定。Android的程序自然也不例外。从Android SDK 0.9开始,就有了比较成熟的测试框架,但是直到目前最新的1.1版本,也没有详细的文档介绍这个内容,只是简单的给了一个Api Demos里的几个单元测试代码。因此,我在这里对此内容做一下梳理和总结: JUnit还能用么?
在Java下做单元测试必然用到JUnit。这里说的JUnit是指从Apache基金会下载的junit.jar里提供的一系列单元测试功能。这些功能显然是运行在JDK之上的。在Android下已经没有了JDK,自然也无法运行JUnit。但是这并不妨碍我们利用JUnit编写单元测试。只不过在运行单元测试时,一定要用JDK来运行,利用java命令来启动JUnit的某个Runner。如果是用Eclipse的话,可以在Run Configuration里新建一个JUnit。但是一定要记得在Classpath选项卡里将Bootstrap Entries中的Android Library改成JRE,并且添加junit.jar。
很明显的,这种测试就是正规的Java单元测试,和Android没有任何关系。你无法测试任何关于Android系统中的API,你写的 Activity,人机界面等等。所以,如果你想测试仅仅是一些封装数据的对象,或者是纯粹的数值计算,还是可以用这种方法的。 Android里面的junit.framework包是怎么回事?
很多人看到这个包的时候,第一反应是Android是不是已经完整集成了JUnit。很遗憾这不是事实。如果你按照JUnit的运行方法,却不像上面那样改用JDK,就一定会得到一个异常:
- #
- #AnunexpectederrorhasbeendetectedbyJavaRuntimeEnvironment:
- #
- #InternalError(classFileParser.cpp:2924 ),pid=4900 ,tid=4476
- #Error:ShouldNotReachHere()
- #
- #JavaVM:JavaHotSpot(TM)ClientVM(10.0 -b19mixedmodewindows-x86)
- #Anerrorreportfilewithmoreinformationissavedas:
- #E:\Mydoc\EclipseWorkspace\TestAndroid\hs_err_pid4900.log
- #
- #Ifyouwouldliketosubmitabugreport,pleasevisit:
- #http://java.sun.com/webapps/bugreport/crash.jsp
- #
#
# An unexpected error has been detected by Java Runtime Environment:
#
# Internal Error (classFileParser.cpp:2924), pid=4900, tid=4476
#Error: ShouldNotReachHere()
#
# Java VM: Java HotSpot(TM) Client VM (10.0-b19 mixed mode windows-x86)
# An error report file with more information is saved as:
# E:\Mydoc\EclipseWorkspace\TestAndroid\hs_err_pid4900.log
#
# If you would like to submit a bug report, please visit:
# http://java.sun.com/webapps/bugreport/crash.jsp
#
实际上,TestCase这个类用于在Android担当所有独特的TestCase的基类的作用,它是一个Abstract Class。Android单元测试类继承关系图如下所示:
之所以有那么多XXXTestCase主要是为了简化工作。例如当你想对一个访问数据库的功能进行测试时,首先需要自己启动并初始化数据库。在这里是类似的,如果你想测试一个Activity,首先要启动它。而ActivityTestCase就会自动帮你做完这些事情。而 ActivityUnitTestCase会更注重测试的独立性,它会让测试与Android底层的联系降到最低。其余的类可以查看相关的Javadoc 来按需挑选。要编写测试,就是找到合适的XXXTestCase作为基类来继承,并且编写自己的测试方法。
很明显的,最简单的编写测试的方法就是继承AndroidTestCase写一个自己的TestCase。然后为自己的一组 TestCase写一个Activity界面,由界面控制TestCase的启动,运行和结果报告。但是,你很快会发现,为何要给测试写一个界面呢?这太诡异了。这时就需要一种技术,它可以利用命令行(Shell)来启动一组测试,并且通过命令行的形式给出结果。这就是所谓的 Instrumentation。 什么是Instrumentation?
一般在开发Android程序的时候,需要写一个manifest文件,其结构是:
- <applicationandroid:icon="@drawable/icon" android:label="@string/app_name" >
- <activityandroid:name=".TestApp" android:label="@string/app_name" >
- ……
- </activity>
- </application>
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".TestApp" android:label="@string/app_name">
……
</activity>
</application>
这样,在启动程序的时候就会先启动一个Application,然后在此Application运行过程中根据情况加载相应的 Activity,而Activity是需要一个界面的。但是Instrumentation并不是这样的。你可以将Instrumentation理解为一种没有图形界面的,具有启动能力的,用于监控其他类(用Target Package声明)的工具类。任何想成为Instrumentation的类必须继承android.app.Instrumentation。下面是这个类的解释:
Base class for implementing application instrumentation code. When running with instrumentation turned on, this class will be instantiated for you before any of the application code, allowing you to monitor all of the interaction the system has with the application. An Instrumentation implementation is described to the system through an AndroidManifest.xml's <instrumentation> tag.
对于单元测试,我们需要认真了解的就是android.test.InstrumentationTestRunner类。这是Android单元测试的主入口。它相当于JUnit当中TestRunner的作用。
那么如何加载它呢,首先要在manifest文件中加入一行关于Instrumentation的声明。比如Android Api Demos中的测试里的manifest是这么写的(我滤掉了所有的注释):
- <manifestxmlns:android="http://schemas.android.com/apk/res/android"
- package ="com.example.android.apis.tests" >
- <application>
- <uses-libraryandroid:name="android.test.runner" />
- </application>
- <instrumentationandroid:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.example.android.apis"
- android:label="TestsforApiDemos." />
- </manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.apis.tests">
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.example.android.apis"
android:label="Tests for Api Demos."/>
</manifest>
如果用Eclipse的ADT插件(0.8版本以上),也可以用图形界面来添加,如下图:
编辑好manifest,就可以打包(build,可以用Eclipse ADT来做,也可以用aapt命令手工完成),然后安装到虚拟机上(用adb install命令)。之后就可以利用命令行的方式来加载你的单元测试了。在Android Shell中加载一个Instrumentation的方法是利用以下命令:
- adbshellaminstrument–wXXXXXX
adb shell am instrument –w XXXXXX
其中-w是指定Instrumentation类的参数标志。一个简单的例子是:
- adbshellaminstrument-wcom.android.foo/android.test.InstrumentationTestRunner
adb shell am instrument -w com.android.foo/android.test.InstrumentationTestRunner
当然,也可以利用adb shell先进入android命令行模式,再直接写am instrument –w XXXXXXX。下面将具体介绍如何将根据需要加载一组单元测试。 如何在Android中利用Instrumentation来进行测试?
在介绍具体的命令之前,我们先理解一下单元测试的层次。一组单元测试可以被组织成若干个TestSuite。每个TestSuite包含若干 TestCase(某个继承android.jar的junit.framework.TestCase的类)。每个TestCase又包含若干个 Test(具体的test方法)。
如果假设com.android.foo是你的测试代码的包的根。当执行以下命令时,会执行所有的TestCase的所有Test。测试的对象就是在Target Package中指定的包中的代码:
- adbshellaminstrument-wcom.android.foo/android.test.InstrumentationTestRunner
adb shell am instrument -w com.android.foo/android.test.InstrumentationTestRunner
如果你想运行一个TestSuite,首先继承android.jar的junit.framework.TestSuite类,实现一个TestSuite(比如叫com.android.foo.MyTestSuite),然后执行以下命令执行此TestSuite
- adbshellaminstrument-eclass com.android.foo.MyTestSuite-wcom.android.foo/android.test.InstrumentationTestRunner
adb shell am instrument -e class com.android.foo.MyTestSuite -w com.android.foo/android.test.InstrumentationTestRunner
其中的-e表示额外的参数,语法为-e [arg1] [value1] [arg2] [value2] …这里用到了class参数。
如果仅仅想运行一个TestCase(比如叫com.android.foo.MyTestCase),则用以下命令:
- adbshellaminstrument-eclass com.android.foo.MyTestCase-wcom.android.foo/android.test.InstrumentationTestRunner
adb shell am instrument -e class com.android.foo.MyTestCase -w com.android.foo/android.test.InstrumentationTestRunner
如果仅仅想运行一个Test(比如就是上面MyTestCase的testFoo方法),很类似的,就这样写: