Android Junit 单元测试

来源:互联网 发布:网络共享打印机脱机 编辑:程序博客网 时间:2024/06/05 06:57

      自动化单元测试可以做许多的事情,并帮你节省时间。它也可以被用作快速检验新建工程或进行冒烟测试。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执行自动化测试。单元测试可以为逻辑测试,功能测试和用户界面测试等,不再是专门手动测试移动应用。

0 0
原创粉丝点击