Android Junit 单元测试

本文介绍如何在Android项目中使用JUnit进行自动化单元测试,包括配置测试环境、创建测试案例及执行测试的方法。涵盖JUnit测试的基本概念、Instrumentation的使用以及如何通过命令行执行不同级别的测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      自动化单元测试可以做许多的事情,并帮你节省时间。它也可以被用作快速检验新建工程或进行冒烟测试。Android SDK支持JUnit的自动化单元测试,其自动化测试框架主要是通过Instrumentation来实现的。

什么是Instrumentation?

1. 一般在开发Android程序的时候,需要写一个manifest文件,在其中声明<application>,在启动程序的时候就会先启动这个application,然后在运行过程中根据情况加载相应的Activity,而Activity是需要一个界面的。但是Instrumentation并不是这样的,你可以将Instrumentation理解为一种没有图形界面的,具有启动能力的,用于监控其他类(用Target Package声明)的工具类。这是Android单元测试的主入口,它相当于JUnit当中TestRunner的作用。对于单元测试,我们需要认真了解的就是android.test.InstrumentationTestRunner类。

2.如何在Android中利用Instrumentation来进行测试?
在介绍具体的命令之前,我们先理解一下单元测试的层次: 一组单元测试可以被组织成若干个TestSuite, 每个TestSuite包含若干TestCase,每个TestCase又包含若干个具体的testMethod.
(1). 如果假设com.android.foo是你的测试代码的包的根, 当执行以下命令时,会执行所有的TestCase的所有Test。测试的对象就是在Target Package中指定的包中的代码:

   adb shell am instrument -w com.android.foo/android.test.InstrumentationTestRunner

      其中-w是指定Instrumentation类的参数标志.

(2).如果你想运行一个TestSuite,首先继承的junit.framework.TestSuite类,实现一个TestSuite(比如叫com.android.foo.MyTestSuite),然后执行以下命令执行此TestSuite

   adb shell am instrument -e class com.android.foo.MyTestSuite -w com.android.foo/android.test.InstrumentationTestRunner
      其中的-e表示额外的参数,语法为-e [arg1] [value1] [arg2] [value2] …这里用到了class参数。

(3). 如果仅仅想运行一个TestCase(比如叫com.android.foo.MyTestCase),则用以下命令:

   adb shell am instrument -e class com.android.foo.MyTestCase -w com.android.foo/android.test.InstrumentationTestRunner

(4). 如果仅仅想运行一个Test(比如就是上面MyTestCase的testFoo方法),就这样写:

   adb shell am instrument -e class com.android.foo.MyTestCase#testFoo -w com.android.foo/android.test.InstrumentationTestRunner

所有的测试结果会输出到控制台,并会做一系列统计,如标记为E的是Error,标记为F的是Failure,Success的测试则会标记为一个点。这和JUnit的语义一致。如果希望断点调试你的测试,只需要直接在代码上加上断点,然后将运行命令参数的-e后边附加上debug true后运行即可。

3.如何在Android的单元测试中做标记?

在android.test.annotation包里定义了几个annotation,包括 @LargeTest,@MediumTest,@SmallTest,@Smoke,和@Suppress。你可以根据自己的需要用这些 annotation来对自己的测试分类。在执行单元测试命令时,可以在-e参数后设置“size large”/ “size medium”/ “size small”来执行具有相应标记的测试。特别的@Supperss可以取消被标记的Test的执行。

下面将详细介绍两种方法:如何创建Android单元测试工程?

1. JUnit测试方法一:在原有工程中配置,生成测试用例


1.1 如上图所示,在原有工程的MainActivity.java 文件上右键单击,选择->New->JUnit Test Case, 并选择父类为android.test.AndroidTestCase (Android测试类要继承AndroidTestCase类),会自动生成MainActivityTest.java,其文件内容如下所示:

package com.test.mytest;

import junit.framework.TestCase;

public class MainActivityTest extends AndroidTestCase {

}

1.2 接下来需要在AndroidManifest.xml中配置JUnit测试环境。主要在Application节点下添加users-library节点,以及instrumentation节点。而且instrumentation的android:targetPackage的值要和AndroidManifest的package属性一样。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.mytest"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <instrumentation
        android:name="android.test.InstrumentationTestRunner"
        android:targetPackage="com.test.mytest" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <uses-library android:name="android.test.runner" />
    </application>

</manifest>
1.3 默认情况下一般<instrumentation>为android 自带的android.test.InstrumentationTestRunner。但也可以自己继承重写InstrumentationTestRunner生成自己的TestRunner,并将<instrumentation>指定为自己的TestRunner。例如下面的代码就重写生成了自己的MyJUnitTestRunner,添加功能将测试结果输出到test_result.xml 文件。
package com.test.mytest;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;

import android.content.Context;
import android.os.Bundle;
import android.os.Environment;

public class MyJUnitTestRunner extends android.test.InstrumentationTestRunner {

    private Writer mWriter;  
    private XmlSerializer mTestSuiteSerializer;  
    private long mTestStarted;  
    private static final String JUNIT_XML_FILE = "test_result.xml";

    @Override  
    public void onStart() {  
        try{  
            File fileRobo = new File(getTestResultDir(getTargetContext()));  
            if (!fileRobo.exists()) {  
                fileRobo.mkdir();  
            }  

            if (isSDCardAvaliable()) {
                File resultFile = new File(getTestResultDir(getTargetContext()),JUNIT_XML_FILE);
                startJUnitOutput(new FileWriter(resultFile));  
            } else {  
                startJUnitOutput(new FileWriter(new File(getTargetContext().getFilesDir(), JUNIT_XML_FILE)));  
            }
        } catch (IOException e){  
            throw new RuntimeException(e);  
        }  

        super.onStart();  
    }  

    @Override  
    public void sendStatus(int resultCode, Bundle results) {
        super.sendStatus(resultCode, results);  
        switch (resultCode) {  
        case REPORT_VALUE_RESULT_ERROR:  
        case REPORT_VALUE_RESULT_FAILURE:  
        case REPORT_VALUE_RESULT_OK:  
            try {  
                recordTestResult(resultCode, results);  
            } catch (IOException e) {  
                throw new RuntimeException(e);  
            }  
            break;  
        case REPORT_VALUE_RESULT_START:  
            recordTestStart(results);  
        default:  
            break;  
        }  
    }

    @Override  
    public void finish(int resultCode, Bundle results) {  
        endTestSuites();  
        super.finish(resultCode, results);  
    }  

    /**
     * Create the XML serializer for writing
     */
    private XmlSerializer newSerializer(Writer writer) {
        try {  
            XmlPullParserFactory pf = XmlPullParserFactory.newInstance();  
            XmlSerializer serializer = pf.newSerializer();  
            serializer.setOutput(writer);  
            return serializer;  
        } catch (Exception e) {  
            throw new RuntimeException(e);  
        }          
    }  

    /**
     * Set the XML file start tag.
     */
    private void startJUnitOutput(Writer writer) {  
        try {  
            mWriter = writer;  
            mTestSuiteSerializer = newSerializer(mWriter);  
            mTestSuiteSerializer.startDocument(null, null);  
            mTestSuiteSerializer.startTag(null, "testsuites");  
            mTestSuiteSerializer.startTag(null, "testsuite");  
        } catch (Exception e) {  
            throw new RuntimeException(e);  
        }  
    }  
    
    /**
     * Set the XML file end tag.
     */
    private void endTestSuites() {  
        try {  
            mTestSuiteSerializer.endTag(null, "testsuites");  
            mTestSuiteSerializer.endDocument();  
            mTestSuiteSerializer.flush();  
            mWriter.flush();
            mWriter.close();
        } catch (IOException e) {  
            throw new RuntimeException(e);  
        }  
    }  

    /** 
     * 判断SD卡是否存在 
     */  
    private boolean isSDCardAvaliable(){  
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);   
    }  

    /** 
     * 获取测试结果报告文件所在的路径 
     * @param context  被测工程的context 
     * @return  返回测试结果报告文件所在的路径 
     */  
    private String getTestResultDir(Context context){  
        String packageName = "/" + context.getPackageName();  
        String filepath = context.getCacheDir().getPath() + packageName;  

        if(android.os.Build.VERSION.SDK_INT < 8){  
            if (isSDCardAvaliable()) {
                filepath = Environment.getExternalStorageDirectory().getAbsolutePath()+ packageName;  
            }  
        } else {  
            if (isSDCardAvaliable()) {  
                filepath = Environment.getExternalStorageDirectory().getAbsolutePath()+ packageName;  
            }  
        } 

        return filepath;  
    }  

    private void recordTestStart(Bundle results) {  
        mTestStarted = System.currentTimeMillis();  
    }  

    private void recordTestResult(int resultCode, Bundle results) throws IOException {
        float time = (System.currentTimeMillis() - mTestStarted) / 1000.0f;  
        String className = results.getString(REPORT_KEY_NAME_CLASS);  
        String testMethod = results.getString(REPORT_KEY_NAME_TEST);  
        String stack = results.getString(REPORT_KEY_STACK);  
        int current = results.getInt(REPORT_KEY_NUM_CURRENT);  
        int total = results.getInt(REPORT_KEY_NUM_TOTAL);  

        mTestSuiteSerializer.startTag(null, "testcase");  
        mTestSuiteSerializer.attribute(null, "classname", className);  
        mTestSuiteSerializer.attribute(null, "name", testMethod);  

        if (resultCode != REPORT_VALUE_RESULT_OK) {
            mTestSuiteSerializer.startTag(null, "failure");  
            if (stack != null) {
                String reason = stack.substring(0, stack.indexOf('\n'));  
                String message = "";  
                int index = reason.indexOf(':');  
                if (index > -1) {  
                    message = reason.substring(index+1);  
                    reason = reason.substring(0, index);  
                }  
                mTestSuiteSerializer.attribute(null, "message", message);  
                mTestSuiteSerializer.attribute(null, "type", reason);  
                mTestSuiteSerializer.text(stack);  
            }  
            mTestSuiteSerializer.endTag(null, "failure");  
        } else {  
            mTestSuiteSerializer.attribute(null, "time", String.format("%.3f", time));  
        }  

        mTestSuiteSerializer.endTag(null, "testcase");

        if (current == total) {
            mTestSuiteSerializer.endTag(null, "testsuite");
            mTestSuiteSerializer.flush();
        }  
    }
}<span style="font-size:12px;">
</span>
1.4 添加继承AndroidTestCase的TestCase类,并添加要测试的代码,可以添加任意函数,该函数会被测试框架自动执行。 测试类中的测试方法必须以test开头,例如:
package com.test.mytest;

import android.test.AndroidTestCase;

public class MainActivityTest extends AndroidTestCase {
    
    public void testAdd() throws Exception {  
        TestClass object = new TestClass();
        int sum = object.add(1, 2);  
        assertEquals(3, sum);  
    } 
}

1.5 然后点击右键选中MainActivityTest.java,选择Run As -> Android Junit Test,开始执行case。

1.6 也可以将所有的test case 添加到test suite一次性执行。需要重写TestRunner 的getAllTests()函数,然后右键点击整个android project,选择Run As -> Android Junit Test

package com.test.mytest;

import junit.framework.TestSuite;

public class TestSuiteRunner extends MyJUnitTestRunner {

    @Override
    public TestSuite getAllTests() {
	    
       TestSuite suite = new TestSuite(TestSuiteRunner.class.getCanonicalName()); 
       
       suite.addTestSuite(com.test.mytest.MainActivityTest.class);
       //add other test class here
       return suite;
   } 
}

2. JUnit测试方法二:创建单独的单元测试工程

    其实这种方式更加简单,这种方式是单独创建一个单元测试的工程来进行测试。即创建一个 Android Test Project ,然后选择需要单元测试的项目就OK了,通过这种方式进行单元测试的话就不用进行上面的配置,其实创建这种工程的时候,默认已经帮我们配置好了。

2.1 创建一个Android的JUnit项目:

    如果你的Eclipse中已经有Android项目,对现有的Android项目,在Eclipse中右键单击,选择“Android Tools”,然后“New Test Project...”。如果是新建Android项目,在"New" -> "Project" -> "Android Test Project",按“Next”按钮...。无论按照上面哪种方式,都会在这个时候创建一个独立的Android测试工程。

2.2 在Android测试工程中创建一个JUnit测试用例:
     Android应用程序通常是由一些Activity类组成的。事实上,每一个Activity都可以是一个独立实体,Android SDK中包含了几个类来测试Activity类。现在我们将使用一个。右键单击你的测试项目,选择“New” -> 然后“JUnit Test Case”:在新建JUnit测试用例对话框上填写,使用父类是android.test.ActivityInstrumentTestCase2, 如下:

点击“完成”按钮,这个类就创建成功了。注意:由向导创建的默认构造函数是不正确的。我们需要修改它,让它不带任何参数,调用不同的super()的方法.setUp()方法中,应配置运行这个测试用例所需的所有东西。Activity实例随时可以被getActivity()方法调用。例如:如果我们想在测试中显示一个TextView在Activity上,我们可以实现setUp()方法,如下:

protected void setUp() throws Exception {
    super.setUp();
    TextView helloText = (TextView) getActivity().findViewById(R.id.hello_textview);
}

2.3 此时你可以创建各种测试,你可以获取到Activity布局上所有控件,以及在应用程序的任何代码。由于使用Activity测试用例,我们可能感兴趣于用户界面,布局,及功能。所有的测试方法必须用“test”做前缀。下面我们创建了一个测试名为“HelloTextVisibility”的方法。如果测试通过或失败,assertFalse()都会被调用。

public void testHelloTextVisibility() {
    View container = getActivity().findViewById(R.id.container_layout);
    int boundaryWidth = container.getWidth();
    int boundaryHeight = container.getHeight();
    int[] location = new int[2];
    container.getLocationOnScreen(location);
    int[] helloTextLocation = new int[2];
    helloText.getLocationOnScreen(helloTextLocation);
    Rect textRect = new Rect();
    helloText.getDrawingRect(textRect);
    
    boolean widerThanBoundary = (textRect.width() > boundaryWidth);
    boolean tallerThanBoundary = (textRect.height() > boundaryHeight);
    boolean extendsOffRight = location[0] + boundaryWidth > helloTextLocation[0] + textRect.width();

    assertTrue("Text wider than boundary", widerThanBoundary);
    assertTrue("Text taller than boundary", tallerThanBoundary);
    assertTrue("Text goes off right side", extendsOffRight);
}

      本文介绍了如何快速添加一个新的测试项目,并在Eclipse的Android项目中使用JUnit对你的app执行自动化测试。单元测试可以为逻辑测试,功能测试和用户界面测试等,不再是专门手动测试移动应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值