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执行自动化测试。单元测试可以为逻辑测试,功能测试和用户界面测试等,不再是专门手动测试移动应用。
- Android:Junit 单元测试方法
- Android Junit单元测试
- Android Junit单元测试
- android中junit单元测试
- Android 使用Junit单元测试
- Android单元测试Junit
- Android:单元测试Junit配置
- Android单元测试 Junit(1)
- junit实现android单元测试
- android junit单元测试
- android JUnit单元测试
- android JUnit单元测试
- Android单元测试jUnit
- android junit单元测试
- Android单元测试之JUnit
- Android Junit 单元测试
- Android JUnit单元测试
- Android中的Junit单元测试
- 深入理解 bash redirection 重定向
- JSP页面多次提交时(后台对表单有校验功能 )提示信息重复出现解决办法
- [LeetCode]Word Search
- 百万级访问网站前期的技术准备
- android面试---JVM必备知识
- Android Junit 单元测试
- (hdu step 8.1.8)Team Queue(一个人排队时,如果前面有他认识的人,那么他可以直接排到他认识的人的后面,否则排在队伍的最后面。求这种情况下的出队情况)
- struts 标签引用出错
- JAVA 基本数据类型长度
- JVM学习笔记之运行时数据区
- iOS开发中ARC环境与非ARC环境混编
- TOF相关总结
- ORACLE学习过程中各种报错的总结
- 【伯乐在线】FACEBOOK产品设计总监:设计APP时的14个必考题