android应用程序能否使用unit程序

3643人阅读
Android(101)
1. Testing for ContentProvider
在你开始为Provider写Case之前,应该仔细读一读SDK文档中关于Provider测试的说明。但是光读那些说明,你还是没办法写出正确的Case,因为你也知道,Android的文档是比较差劲的,有一些关键东西文档中没有说明,你也知道,这在Android当中并不少见。
你写个Provider的Case,如下:
public class DemoProviderTest extends ProviderTestCase2&FeedProvider& {
}编译有错误,它说ProviderTestCase2没有隐式的构造,看来我们需要一个构造函数,写一个标准的JUnit构造吧!
public class DemoProviderTest extends ProviderTestCase2&FeedProvider& {
public FeedProviderTest(String name) {
super(name);
}WTF,还是有编译错误,而且更严重!难道ProviderTestCase2不是继承自TestCase,用了Eclipse的建议,它创建了一个带有二个参数的构造:
public class DemoProviderTest extends ProviderTestCase2&FeedProvider& {
public FeedProviderTest(String name) {
super(name);
public DemoProviderTest(Class&FeedProvider& providerClass,
String providerAuthority) {
super(providerClass, providerAuthority);
// TODO Auto-generated constructor stub
}但是仅一个名字的FeedProviderTest(String name)还是有错误,再试试不带参数的,还是不行,这说明ProviderTestCase2没有这样的构造函数,但是没有道理啊,因为它毕竟是继承自TestCase的啊!很神奇和诡异啊!
既然ProviderTestCase2没有一个参数的构造,那么只能去掉带有一参数的构造了!
public class DemoProviderTest extends ProviderTestCase2&FeedProvider& {
public DemoProviderTest(Class&FeedProvider& providerClass,
String providerAuthority) {
super(providerClass, providerAuthority);
public void testConstructor() throws Throwable {
assertNotNull(&can construct resolver&, getMockContentResolver());
ContentProvider provider = getProvider();
assertNotNull(&can instantiate provider&, provider);
}写了一个基本的测试,运行了下,得到了一个Warning,是由JUnit Framework报出来的说DemoProviderTest没有定义公共的构造函数TestCase(name)或TestCase(),什么情况,不是我不定义而是有编译错误啊,因为该死的ProviderTestCase2没有这二个构造!该死,只能再把这个构造加回来!但是因为父类没有,只能引用父类的双参数的构造了!
public class DemoProviderTest extends ProviderTestCase2&FeedProvider& {
public DemoProviderTest() {
super(null, null);
public DemoProviderTest(Class&FeedProvider& providerClass,
String providerAuthority) {
super(providerClass, providerAuthority);
public void testConstructor() throws Throwable {
assertNotNull(&can construct resolver&, getMockContentResolver());
ContentProvider provider = getProvider();
assertNotNull(&can instantiate provider&, provider);
}但是参数传什么呢?先用Null试试中吧!完全有错误,在父类的构造初始化时出现了NPE,这说明传Null肯定是不对的!看了下强加的带有二个参数的构造DemoProviderTest(Class&FeedProvider& providerClass, String providerAuthority),也说应该传一个Class对象,和Provider的Authority,再试试看!
public class DemoProviderTest extends ProviderTestCase2&FeedProvider& {
public DemoProviderTest() {
super(FeedProvider.class, AUTHORITY);
public DemoProviderTest(Class&FeedProvider& providerClass,
String providerAuthority) {
super(providerClass, providerAuthority);
public void testConstructor() throws Throwable {
assertNotNull(&can construct resolver&, getMockContentResolver());
ContentProvider provider = getProvider();
assertNotNull(&can instantiate provider&, provider);
}这次Okay了,但是这样一来二个参数的构造就没有意义了,于是让一个参数的调用二个参数的:
public DemoProviderTest() {
this(FeedProvider.class, AUTHORITY);
}还是Okay,这说明我们的Case必须给ProviderTestCase2提供正确的构造参数!
再加上setUp和tearDown:
public void setUp() throws Exception {
mContentResolver = getMockContentResolver();
public void tearDown() throws Exception {
mContentResolver =
}运行,发现testConstructor挂了,说getMockContentResolver()返回的是Null,这怎么可能啊,太诡异了!想到还是可能初始化未正确,给setUp加上了父类的调用:
public void setUp() throws Exception {
super.setUp();
mContentResolver = getMockContentResolver();
public void tearDown() throws Exception {
super.tearDown();
mContentResolver =
}这下再跑,全都Okay了,说明凡是涉及到重写(Override)父类的方法,都要调用父类的方法,以期正确初始化!下面是正确的完整版:
public class DemoProviderTest extends ProviderTestCase2&FeedProvider& {
private ContentResolver mContentR
public DemoProviderTest() {
this(FeedProvider.class, AUTHORITY);
public DemoProviderTest(Class&FeedProvider& providerClass,
String providerAuthority) {
super(providerClass, providerAuthority);
public void setUp() throws Exception {
super.setUp();
mContentResolver = getMockContentResolver();
public void tearDown() throws Exception {
super.tearDown();
mContentResolver =
public void testConstructor() throws Throwable {
assertNotNull(&can construct resolver&, getMockContentResolver());
ContentProvider provider = getProvider();
assertNotNull(&can instantiate provider&, provider);
}总结一下,从这个例子得到的经验是,对于组件的测试,都要继承自android.test.*下面的组件测试框架,但是需要给这些组件测试框架传递正确的参数,否则Case无法测试:
二个构造函数
public DemoProviderTest() {
this(FeedProvider.class, AUTHORITY);
public DemoProviderTest(Class&FeedProvider& providerClass,
String providerAuthority) {
super(providerClass, providerAuthority);
}一个都不能少,而且是JUnit的指定构造函数(带有一个String,或不带参数的)要调用测试架构指定的构造,以给测试框架传递正确的参数!
还有就是重写的父类方法时,一定要把父类的方法也调用上,否则还是不会初始化正确!
但是这里不得不说这些组件测试框架写的真是不好用,首先,那个名字就让人费解,为什么有个2啊!Android真够2的!还有,既然作为框架,应该把初始化的工作做完整,做彻底,这样才能称的上框架。使用者应该只需要继承,把自己的事情做完,就应该能进行工作,就像组件Activity或ContentProvider一样,到了你的代码里的时候,框架里的初始化工作已经做完,所以你,继承者只需要关心你自已的初始化工作就好!但是测试框架就烂,继承者不但要关心自己的初始化还要保证给父类传递正确的参数!
2. Testing for Activity
同样对于Activity的测试也是要注意初始化的部分,只不过对于setUp和tearDown你不调super也没有关系!
public class ExplorerActivityTester extends
ActivityInstrumentationTestCase2&ExplorerActivity& {
public ExplorerActivityTester() {
this(TARGET_PACKAGE_NAME, ExplorerActivity.class);
public ExplorerActivityTester(String pkg, Class&ExplorerActivity& class1) {
super(pkg, class1);
public void setUp() {
mInstrumentation = getInstrumentation();
3. Obstacles to unit testing
在Android里面,由于其系统架构的特性决定了给Android写单元测试用例和验证测试用例特别因难
a. Activity reuse
原因就是每一个测试的包,测试的包也是一个Apk,每一个包只能注入一个目标Apk,也就是说只能针对一个Apk里面的内容进行测试,一旦某个操作跳到了Apk以外的地方,就超出了测试框架的控制范围。但是组件重用机制在Android中非常的普遍,通过Intent来跳到其他的应用(apk)中,调用其他应用的组件来完成某个操作,这是Android的特性,是再普遍不过的了!但这就给单元测试用例埋下了无法逾越的障碍。测试框架本身更弱,一但跳出了某个组件,Instrumentation便无法对其进行控制,开源测试框架robutium-solo一定程度上解决了这个问,Solo可以操作一个包内的任何组件,特别地它能够解决多个Activity跳转的问题,但是如前所述,因为一个测试Apk只能注入一个目标Apk,所以一旦Activity跳到了应用外,Solo也没有了办法。这是一个无解的问题。因此,Android当中做测试,只能关注一些逻辑层,API层,数据和Provider,Service等一些与表层操作较远的代码!对于表层Activity跳来跳去的情况,只能做部分测试,或用MockObject来解决,但是这通常失去了测试的本身意义,因为要花大量时间去创建MockObject,不值!
b. ActionBar is not clickable
还有一非常恶心的问题是,对于Activity的ActionBar无法直接点击,真的不明白Google到底在搞什么,弄出来个新东东,竟然测试框架里面不支持操作!想到点击ActionBar只能通过Solo来点击屏幕坐标,这非常难以移植和维护!
说到操作,还不得不说原生框架Instrumentation支持的操作非常少,而且不好用,它只能派发KeyEvent事件,很多情况下都不好用,比如有个对话框,想要点击Okay或是Cancel的话,就很麻烦,再如想点击一个ListView中的某一项的话也是非常麻烦!同样第三方的robotium-solo框架就好用多了,它进行了很好的封装,通过Solo.clickOnText()就可以方便的点击屏幕上的带有此文字的View。它的内部实现方式是通过View的显示Tree,根据Tag(文字)来查找相关的View,然后对其发送点击事件!这也解释了为什么Solo也无法点击ActionBar,因为ActionBar不是在Activity的View中,它是像StatusBar一样,属于系统级别的东西!
c. StatusBar belongs to Settings.apk
难以想象吧,随处可见的Statusbar竟然以属于Settings,只有注入了Settings的包才能对Statusbar进行操作。所以虽然Statusbar上面有你的Apk的相关的东西(比如提示)但是你还是无法直接操作它,除非你写一个专门注入Settings.apk的测试包!
4. Security Concern
测试的代码(Instrumentation和TestRunner)也是以一个Apk的形式存在的,它可注入任何目标Apk,然后就可以对其进行操作,甚至获取其资源和数据。这就带来了安全上面的问题!可以把一个带有测试代码的Apk当成一个应用,一旦在某个手机运行,但可以操作任何一个应用。
其实,这本来不是问题,如果应用市场能对开发者上传的应用进行严格的测试和审核。但是现在的问题是无论是Google Play还是其他市场都不怎么测试,所以就会让不良者有机可乘!
其实,这里的关键问题在于,Android厂商不要盲目的追求数量!把应用集中销售是Apple想出来的主意,Apple的App Store也是做的最好的!Android只是一个效仿者,所以你发展的慢,数量不多,质量不够,收入不好,是正常的,因为你是一个追随者,你起步晚!对于厂商来讲,数量你没有办法控制,无法一下子弄出几万个应用来,这个是需要时间的,但是,至少,你可以严格控制质量啊!你可以做到对上传的应用进行严格的测试,这是对用户负责,也是对自己负责啊!所以无论是设备还是应用程序,都是Apple的要优质一些,Android总是要残次一些,所以你看Apple的东西价格就高,Android就便宜,当然价格也是Android的唯一优势!现的社会是一分钱一分货,便宜自然就没好货!
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1147606次
积分:13538
积分:13538
排名:第467名
原创:208篇
转载:38篇
译文:10篇
评论:657条
欢迎访问并收藏新的博客地址:
(2)(1)(5)(3)(3)(2)(2)(6)(1)(1)(1)(3)(3)(3)(2)(8)(5)(1)(2)(2)(6)(1)(2)(6)(14)(5)(9)(9)(2)(8)(6)(11)(8)(4)(33)(2)(4)(1)(1)(1)(10)(2)(1)(27)(2)(8)(7)(6)(3)(1)输入关键字进行搜索
Gradle Unit TestAndroid Studio 1.1 正式版本已经发布, 1.1 版本最大的新功能就是正式支持 Unit 。所谓支持 Unit 是指 Gradle 的 Unit 支持。本文中的内容,也需要在 Android Studio 1.1 中才能正常执行。
的个人理解和总结。不足的地方,还望指正。
环境设置讲解1
在默认情况下,Android Studio 只开启了 Android Instrumentation Test ,需要手动开启 Unit Test 。
Settings-->Gradle-->Experimental ,勾选 Enable Unit Testing support 。
在 Build Variants 面板中,选择 Unit Tests 。
当你在在 Build Variants 面板中,选择 Unit Tests 时,可能会有如下的提示:
其实,你可能在上一步勾选 Enable Unit Testing support 时就已经发现了,Android Studio 已经提示了, Unit Testing 需要 Android Gradle plugin 1.1 以及以上版本,所以我们需要设置 {@projectName}/build.gradle 中的 Android Gradle plugin 版本为 1.1 ,如果你的 Android Gradle plugin 已经为 1.1 或者以上版本,可以跳过此步。
buildscript&{
repositories&{
&&&&jcenter()
dependencies&{
&&&&classpath&'com.android.tools.build:gradle:1.1.0'
&&&&//&NOTE:&Do&not&place&your&application&dependencies&&they&belong
&&&&//&in&the&individual&module&build.gradle&files
allprojects&{
repositories&{
&&&&jcenter()
Java 在做 Unit Test 的时候,最常用的便是 JUnit 了,所以我们需要加入 JUnit 的依赖。在 {@projectName}/{@moduleName}/build.gradle 中添加 JUnit 的 Maven 依赖。
dependencies&{
testCompile&'junit:junit:4.12'
testCompile 意思为,test模式下依赖某一个库,该库不会在我们正式发布时打包到我们的程序中,作用和 debugCompile 类似。
在 {@moduleName}/src 目录下创建 test/java 文件夹。该文件夹是 Unit Test 代码存放的位置。
测试代码的包名、类名、方法名均可以自定义,不用和被测试代码相同。但是为了方便自己和他人,建议使用一一对应的方式建立原代码和测试代码的关系。
编写测试讲解6
Demo GradleUnitTest 是一个测试 Utils 类中 add 方法的项目,借助测试该类我们学习 Unit Test 的基本使用。该类的具体文件路径为 {@projectName}/{@moduleName}/src/main/java/cc/bb/aa/gradleunittest/util/Utils.java 。
public&class&Utils
public&static&int&add(int&a,&int&b)
&&&&return&a&+&b;
我们为 Utils 类编写一个测试类,类名为 UtilsTest ,该类具体的具体文件路径为 {@projectName}/{@moduleName}/src/test/java/cc/bb/aa/gradleunittest/util/UtilsTest.java 。
public&class&UtilsTest
public&void&add()
&&&&Assert.assertTrue(3&==&Utils.add(1,&2));
其中 @Test 为 JUnit 的注解,用于标注测试方法。关于更多 JUnit 的知识,请自行搜索学习。
我们可以在 Build Variants 面板中切换 Unit Tests 和 Android Instrumentation Test 来观察 Project 面板中项目目录的显示情况。
勾选 Android Instrumentation Test 时:
勾选 Unit Tests 时:
执行测试JUnit讲解7
选择 Edit configurations
添加一个 JUnit
删除 Before launch 中的 Make ,添加一个 Gradle-aware make ,Task 可以不用填写,直接点击 OK 。
你可以在 Configuration 中设置测试过滤。例如,我在 Test kind 中选择 All in directory ,并且在 Directory 中选择了 {@projectName}/{@moduleName}/src/test/java 文件夹,这样便可以一次性测试所有的测试代码。
需要在 Use classpath of module 中选择需要测试的 module 。
最终的设置结果如下:
点击 OK ,运行刚刚设置的 JUnit ,便可以观察到测试结果。
Gradle Unit讲解8
我们除了在 Run/Debug Configurations 中添加 JUnit 之外,还可以在 Gradle 面板中执行 test 命令。
双击 Gradle 面板中执行 test 命令,发现该命令除了在 Message 和 Run 面板中输入大段大段的文字,并没有什么具体的反应。其实,测试的结果放在了一个目录下面。
在 {@projectName}/{@moduleName}/build/reports/tests 中我们可以看到 debug 和 release 两个文件夹,分别对应的是 debug 和 release 这两个 BuildTypes 的测试结果( Gradle 的 test 命令实际包含了testDebug 和 testRelease 两个命令)。在这两个文件夹中,就是各自的测试结果。
例如,在 debug 文件夹中,我们打开 index.html 就可以查看测试结果了。
执行 test 命令时,如果 testDebug 出现了断言错误,命令将停止,不再继续 testRelease 。如果你想一次性执行所有的测试,即使出现了断言错误也要继续 testRelease ,请看 讲解9 。
你也可以在命令行中使用 Gradle 命令执行 Unit Test 。
例如,讲解8 中的 test 命令,我们可以在命令行中执行 ./gradlew test 完成。
执行 test 命令时,如果 testDebug 出现了断言错误,命令将停止,不再继续 testRelease 。如果你想一次性执行所有的测试,即使出现了断言错误也要继续的话,你可以使用 ./gradlew test --continue 命令。这样,所以的测试结果都会输出到 {@projectName}/{@moduleName}/build/reports/tests 目录,包括断言错误的内容。
如果你想单独测试某个类,你可以添加 --tests 参数。例如: ./gradlew testDebug --tests='*.MyTestClass' 。
在命令行中使用 Gradle 命令是一件很痛苦的事情,因为它会下载项目构建环境依赖(~~因为 GFW ,你还可能下载失败~~)。而这些依赖已经存在 Android Studio 中了。没有使用已经存在的依赖,是因为在命令行中执行 Gradle 命令已经脱离了 Android Studio,就是纯粹地使用 Gradle (Android
Studio Gradle plugin 的作用便在于此)。Flavors 和 BuildTypes本节内容,请参考 GradleUnitTest2 中的代码。
Gradle 支持 Flavors 和 BuildTypes ,因此要有对应的测试。
Flavors 和 BuildTypes 中的代码需要和对应分支下的测试类有对应的目录关系。 GradleUnitTest2 中对应的关系如下:
src/main/java/cc/bb/aa/gradleunittest/util/Utils.java
src/test/java/cc/bb/aa/gradleunittest/util/UtilsTest.java
src/amazon/java/cc/bb/aa/gradleunittest/util/ProductFlavors.java
src/testAmazon/java/cc/bb/aa/gradleunittest/util/ProductFlavorsTest.java
src/google/java/cc/bb/aa/gradleunittest/util/ProductFlavors.java
src/testGoogle/java/cc/bb/aa/gradleunittest/util/ProductFlavorsTest.java
src/debug/java/cc/bb/aa/gradleunittest/util/BuildTypes.java
src/testDebug/java/cc/bb/aa/gradleunittest/util/BuildTypesTest.java
src/release/java/cc/bb/aa/gradleunittest/util/BuildTypes.java
src/testRelease/java/cc/bb/aa/gradleunittest/util/BuildTypesTest.java
因此,Gradle 命令不再简简单单只有 test 、 testDebug 、 testRelease 3个,现在为 test 、 testAmazonDebug 、 testAmazonRelease 、 testGoogleDebug 、 testGoogleRelease 。同样的道理,test 命令包含了其他4个命令。
其他内容讲解11
Unit Test 应该是尽可能独立的。对一个 class 的 Unit Test 不应该再和其他 class 有任何交互。
用于运行单元测试的 android.jar 文件不包含任何实际的代码(实际的代码由程序所运行在的 Android 真实设备提供),相反,所有的方法抛出异常(默认情况下)。需要确保您的单元测试只是测试你的代码,不依赖于 Android 平台的任何特定行为。如果你没有使用虚拟依赖生产测试框架(例如: Mockito ),你需要在 {@projectName}/{@moduleName}/build.gradle 文件中添加以下内容:
testOptions&{&
unitTests.returnDefaultValues&=&true
例如,我们在测试代码中使用了 TextUtils.isEmpty 方法。在测试时,该方法会出现 java.lang.RuntimeException: Method isEmpty in android.text.TextUtils not mocked. 异常。当我们设置了 unitTests.returnDefaultValues = true ,无论传入的参数是什么,TextUtils.isEmpty 方法永远返回 false 。
要回复文章请先或

我要回帖

更多关于 android应用程序 的文章

 

随机推荐