如果想在android里面做单元测试,有以下三种方法可行。
第一, 就是java程序员最为熟悉和常用的JUnit, android sdk如果用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。
第三,利用android提供的androidTestCase,通过继承这个类来实现自己的test case,然后自己为test设计UI,该方法具体用法放在了另外一篇博客中,可以点击下面的链接阅读:
下面通过一个实例来看一下如何通过Instrumentation来做单元测试。
Step 1. 首先编写需要测试的activity:
de>package com.android.ut; 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; } } de> |
Step 2.
接下来编写测试类,其中主要来测试add()方法。我们在当前代码目录下,在新建一个文件夹,命名为test,并在里面新建了包com.android.ut.test。然后往里面新增加一个class.具体如下:
de>package com.android.ut.test; 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)); } } de> |
Step 3.最后一步就是要改一下Manifest文件。
de><?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> de> |
需要注意的是,在这里面我加上了:
<uses-library android:name="android.test.runner" />
以及:
<instrumentation android:targetPackage="com.android.ut" android:name="android.test.InstrumentationTestRunner" android:label="Test Unit Tests"></instrumentation>
Step 4. 运行
首先通过模拟器运行一下AndroidUT,然后在命令行终端中运行
adb shell am instrument -e class com.android.ut.test.TestApp -w com.android.ut/android.test.InstrumentationTestRunner
这样你就可以看到测试结果了。
通过AndroidTestCase来进行android 单元测试 part I
在以前的博客中介绍过了如何用intrumentation进行android单元测试,其实还有一种方法同样可以,那就是利用AndroidTestCase来做单元测试,intrumentationTestCase和AndroidTestCase都是Junit.framwork.TestCase的子类,二者代表不用的方向。
如果想通过AndroidTestCase,大致可以通过以下几个步骤实现:
1. 添加自己的test case code, 让他们继承自AndroidTestCase。
2. 定义自己的testSuite类,用来管理test cases.
3. 定义自己的testRunner,用来执行测试
下面首先来看一下这种方法所涉及到的android的类以及接口。
AndroidTestCase
Android test cases classes需要从这个类派生出来,而不再是从junit.framework.TestCase. 二者之间的最主要区别就是Android test cases提供了一个方法getContext()来获取当前的上下文变量,这在android测试中很重要的,因为很多的android api都需要context。
AndroidTestCase主要成员:
setUp() //Sets up the fixture, for example, open a network connection.
tearDown() //Tears down the fixture, for example, close a network connection.
testAndroidTestCaseSetupProperly()
TestSuite (in package junit.package)
一个de>TestSuitede>de>就是一系列de>de>test casede>de>的集合。通过de>de>testsuitede>de>可以更好的来管理de>de>test casede>
TestSuite主要成员:
Void
addTest (Test test) //Adds a test to the suite.
void
addTestSuite(Class testClass)
//Adds the tests from the given class to the suite
下面是一小段往test suite中添加test case的示例:
TestSuite suite= new TestSuite();
suite.addTest(new MathTest("testAdd")); //Adds a test to the suite.
suite.addTest(new MathTest("testDivideByZero"));
或者可以通过addTestSuite()来添加:
suite.addTestSuite(MathTest.class);
TestListener (in package junit.framework)
这是一个 interface ,用来监听测试进程
有以下4个Public Methods
:
abstract void
addError(Test test,Throwable t)
An error occurred.
abstract void
addFailure(Test test,AssertionFailedError t)
A failure occurred.
abstract void
endTest(Test test)
A test ended.
abstract void
startTest(Test test)
A test started.
AndroidTestRunner
继承自class junit.runner.BaseTestRunner,但是它没有提供ui, 甚至来一个基于console的UI都没有,所以,如果想要很好的查看测试结果的话,你需要自己来处理来自于test runner的callback 函数。一会可以通过例子演示一下
AndroidTestRunner主要方法:
SetTest();
runTest()
addTestListener()
setContext()
如果要使用AndroidTestRunner,需要在permission in manifest.xml中添加权限:
<uses-library android:name="android.test.runner" />
最后,通过一个实例来演示一下:
1. 写一个test case:
MathTest.java
de>package com.ut.prac; import android.test.AndroidTestCase; import android.util.Log; public class MathTest extends AndroidTestCase { protected int i1; protected int i2; static final String LOG_TAG = "MathTest"; public void setUp() { i1 = 2; i2 = 3; } public void testAdd() { Log.d( LOG_TAG, "testAdd" ); assertTrue( LOG_TAG+"1", ( ( i1 + i2 ) == 5 ) ); } public void testAndroidTestCaseSetupProperly() { super.testAndroidTestCaseSetupProperly(); Log.d( LOG_TAG, "testAndroidTestCaseSetupProperly" ); } }de> |
2. 定义一个test suite类。
ExampleSuite.java
de>package com.ut.prac;
import junit.framework.TestSuite;
public class ExampleSuite extends TestSuite {
public ExampleSuite() {
addTestSuite( MathTest.class );
}
}de>
3. 定义test runner,以及构建UI来处理测试流程,查看测试结果等。
MyJUnitExample.java
de>package com.ut.prac; import junit.framework.Test; import junit.framework.TestListener; import android.app.Activity; import android.os.Bundle; import android.test.AndroidTestRunner; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; public class MyJUnitExample extends Activity { static final String LOG_TAG = "junit"; Thread testRunnerThread = null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button launcherButton = (Button)findViewById( R.id.launch_button ); launcherButton.setOnClickListener( new View.OnClickListener() { public void onClick( View view ) { startTest(); } } ); } private synchronized void startTest() { if( ( testRunnerThread != null ) && !testRunnerThread.isAlive() ) testRunnerThread = null; if( testRunnerThread == null ) { testRunnerThread = new Thread( new TestRunner( this ) ); testRunnerThread.start(); } else Toast.makeText( this, "Test is still running", Toast.LENGTH_SHORT).show(); } } class TestDisplay implements Runnable { public enum displayEvent { START_TEST, END_TEST, ERROR, FAILURE, } displayEvent ev; String testName; int testCounter; int errorCounter; int failureCounter; TextView statusText; TextView testCounterText; TextView errorCounterText; TextView failureCounterText; public TestDisplay( displayEvent ev, String testName, int testCounter, int errorCounter, int failureCounter, TextView statusText, TextView testCounterText, TextView errorCounterText, TextView failureCounterText ) { this.ev = ev; this.testName = testName; this.testCounter = testCounter; this.errorCounter = errorCounter; this.failureCounter = failureCounter; this.statusText = statusText; this.testCounterText = testCounterText; this.errorCounterText = errorCounterText; this.failureCounterText = failureCounterText; } public void run() { StringBuffer status = new StringBuffer(); switch( ev ) { case START_TEST: status.append( "Starting" ); break; case END_TEST: status.append( "Ending" ); break; case ERROR: status.append( "Error: " ); break; case FAILURE: status.append( "Failure: " ); break; } status.append( ": " ); status.append( testName ); statusText.setText( new String( status ) ); testCounterText.setText( "Tests: "+testCounter ); errorCounterText.setText( "Errors: "+errorCounter ); failureCounterText.setText( "Failure: "+failureCounter ); } } class TestRunner implements Runnable,TestListener { static final String LOG_TAG = "TestRunner"; int testCounter; int errorCounter; int failureCounter; TextView statusText; TextView testCounterText; TextView errorCounterText; TextView failureCounterText; Activity parentActivity; public TestRunner( Activity parentActivity ) { this.parentActivity = parentActivity; } public void run() { testCounter = 0; errorCounter = 0; failureCounter = 0; statusText = (TextView)parentActivity. findViewById( R.id.status ); testCounterText = (TextView)parentActivity. findViewById( R.id.testCounter ); errorCounterText = (TextView)parentActivity. findViewById( R.id.errorCounter ); failureCounterText = (TextView)parentActivity. findViewById( R.id.failureCounter ); Log.d( LOG_TAG, "Test started" ); AndroidTestRunner testRunner = new AndroidTestRunner(); testRunner.setTest( new ExampleSuite() ); testRunner.addTestListener( this ); testRunner.setContext( parentActivity ); testRunner.runTest(); Log.d( LOG_TAG, "Test ended" ); } // TestListener public void addError(Test test, Throwable t) { Log.d( LOG_TAG, "addError: "+test.getClass().getName() ); Log.d( LOG_TAG, t.getMessage(), t ); ++errorCounter; TestDisplay td = new TestDisplay( TestDisplay.displayEvent.ERROR, test.getClass().getName(), testCounter, errorCounter, failureCounter, statusText, testCounterText, errorCounterText, failureCounterText ); parentActivity.runOnUiThread( td ); } public void endTest(Test test) { Log.d( LOG_TAG, "endTest: "+test.getClass().getName() ); TestDisplay td = new TestDisplay( TestDisplay.displayEvent.END_TEST, test.getClass().getName(), testCounter, errorCounter, failureCounter, statusText, testCounterText, errorCounterText, failureCounterText ); parentActivity.runOnUiThread( td ); } public void startTest(Test test) { Log.d( LOG_TAG, "startTest: "+test.getClass().getName() ); ++testCounter; TestDisplay td = new TestDisplay( TestDisplay.displayEvent.START_TEST, test.getClass().getName(), testCounter, errorCounter, failureCounter, statusText, testCounterText, errorCounterText, failureCounterText ); parentActivity.runOnUiThread( td ); } @Override public void addFailure(Test test, junit.framework.AssertionFailedError t) { // TODO Auto-generated method stub Log.d( LOG_TAG, "addFailure: "+test.getClass().getName() ); Log.d( LOG_TAG, t.getMessage(), t ); ++failureCounter; TestDisplay td = new TestDisplay( TestDisplay.displayEvent.FAILURE, test.getClass().getName(), testCounter, errorCounter, failureCounter, statusText, testCounterText, errorCounterText, failureCounterText ); parentActivity.runOnUiThread( td ); } } de> |
4. 最后是xml文件
Manifest.xml
de><?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ut.prac" android:versionCode="1" android:versionName="1.0"> <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <uses-library android:name="android.test.runner" /> <activity android:name=".MyJUnitExample" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="3" /> </manifest> de> |
Mail.xml
de><?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:id="@+id/launch_button" android:text="@string/launch_test" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/status" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" /> <TextView android:id="@+id/testCounter" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" /> <TextView android:id="@+id/errorCounter" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" /> <TextView android:id="@+id/failureCounter" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" /> </LinearLayout> de> |
最后编译,执行。。。