如果想在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 {
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> |
定义一个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> |
最后是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> |
|