求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
android junit
 

发布于2012-5-29

 

如果想在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>

相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程

 
分享到
 
 
     


android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...