热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入探讨UnitTestinginAndroid

本篇文章是对UnitTestinginAndroid进行了详细的分析介绍,需要的朋友参考下
1. Testing for ContentProvider
在你开始为Provider写Case之前,应该仔细读一读SDK文档中关于Provider测试的说明。但是光读那些说明,你还是没办法写出正确的Case,因为你也知道,Android的文档是比较差劲的,有一些关键东西文档中没有说明,你也知道,这在Android当中并不少见。
你写个Provider的Case,如下:
代码如下:

public class DemoProviderTest extends ProviderTestCase2 {
}

编译有错误,它说ProviderTestCase2没有隐式的构造,看来我们需要一个构造函数,写一个标准的JUnit构造吧!
代码如下:

public class DemoProviderTest extends ProviderTestCase2 {
    public FeedProviderTest(String name) {
        super(name);
    }
}

WTF,还是有编译错误,而且更严重!难道ProviderTestCase2不是继承自TestCase,用了Eclipse的建议,它创建了一个带有二个参数的构造:
代码如下:

public class DemoProviderTest extends ProviderTestCase2 {
    public FeedProviderTest(String name) {
        super(name);
    }

    public DemoProviderTest(Class providerClass,
            String providerAuthority) {
        super(providerClass, providerAuthority);
        // TODO Auto-generated constructor stub
    }
}

但是仅一个名字的FeedProviderTest(String name)还是有错误,再试试不带参数的,还是不行,这说明ProviderTestCase2没有这样的构造函数,但是没有道理啊,因为它毕竟是继承自TestCase的啊!很神奇和诡异啊!
既然ProviderTestCase2没有一个参数的构造,那么只能去掉带有一参数的构造了!
代码如下:

public class DemoProviderTest extends ProviderTestCase2 {
    public DemoProviderTest(Class 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
    public DemoProviderTest() {
        super(null, null);
    }

    public DemoProviderTest(Class 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 providerClass, String providerAuthority),也说应该传一个Class对象,和Provider的Authority,再试试看!
代码如下:

public class DemoProviderTest extends ProviderTestCase2 {
    public DemoProviderTest() {
        super(FeedProvider.class, AUTHORITY);
    }

    public DemoProviderTest(Class 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:
代码如下:

    @Override
    public void setUp() throws Exception {
        mCOntentResolver= getMockContentResolver();
    }

    @Override
    public void tearDown() throws Exception {
        mCOntentResolver= null;
    }

运行,发现testConstructor挂了,说getMockContentResolver()返回的是Null,这怎么可能啊,太诡异了!想到还是可能初始化未正确,给setUp加上了父类的调用:
代码如下:

    @Override
    public void setUp() throws Exception {
        super.setUp();
        mCOntentResolver= getMockContentResolver();
    }

    @Override
    public void tearDown() throws Exception {
        super.tearDown();
        mCOntentResolver= null;
    }

这下再跑,全都Okay了,说明凡是涉及到重写(Override)父类的方法,都要调用父类的方法,以期正确初始化!下面是正确的完整版:
代码如下:

public class DemoProviderTest extends ProviderTestCase2 {
    private ContentResolver mContentResolver;

    public DemoProviderTest() {
        this(FeedProvider.class, AUTHORITY);
    }

    public DemoProviderTest(Class providerClass,
            String providerAuthority) {
        super(providerClass, providerAuthority);
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();
        mCOntentResolver= getMockContentResolver();
    }

    @Override
    public void tearDown() throws Exception {
        super.tearDown();
        mCOntentResolver= null;
    }

    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 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 {
    public ExplorerActivityTester() {
        this(TARGET_PACKAGE_NAME, ExplorerActivity.class);
    }

    public ExplorerActivityTester(String pkg, Class class1) {
        super(pkg, class1);
    }

    @Override
    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的唯一优势!现的社会是一分钱一分货,便宜自然就没好货!

推荐阅读
  • 本文介绍了互联网思维中的三个段子,涵盖了餐饮行业、淘品牌和创业企业的案例。通过这些案例,探讨了互联网思维的九大分类和十九条法则。其中包括雕爷牛腩餐厅的成功经验,三只松鼠淘品牌的包装策略以及一家创业企业的销售额增长情况。这些案例展示了互联网思维在不同领域的应用和成功之道。 ... [详细]
  • 本文介绍了在Ubuntu 11.10 x64环境下安装Android开发环境的步骤,并提供了解决常见问题的方法。其中包括安装Eclipse的ADT插件、解决缺少GEF插件的问题以及解决无法找到'userdata.img'文件的问题。此外,还提供了相关插件和系统镜像的下载链接。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 延迟注入工具(python)的SQL脚本
    本文介绍了一个延迟注入工具(python)的SQL脚本,包括使用urllib2、time、socket、threading、requests等模块实现延迟注入的方法。该工具可以通过构造特定的URL来进行注入测试,并通过延迟时间来判断注入是否成功。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • 本文介绍了一个免费的asp.net控件,该控件具备数据显示、录入、更新、删除等功能。它比datagrid更易用、更实用,同时具备多种功能,例如属性设置、数据排序、字段类型格式化显示、密码字段支持、图像字段上传和生成缩略图等。此外,它还提供了数据验证、日期选择器、数字选择器等功能,以及防止注入攻击、非本页提交和自动分页技术等安全性和性能优化功能。最后,该控件还支持字段值合计和数据导出功能。总之,该控件功能强大且免费,适用于asp.net开发。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • 本文详细介绍了Mybatis中#与$的区别及其作用。#{}可以防止sql注入,拼装sql时会自动添加单引号,适用于单个简单类型的形参。${}则将拿到的值直接拼装进sql,可能会产生sql注入问题,需要手动添加单引号,适用于动态传入表名或字段名。#{}可以实现preparedStatement向占位符中设置值,自动进行类型转换,有效防止sql注入,提高系统安全性。 ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • 模块化区块链生态系统的优势概述及其应用案例
    本文介绍了相较于单体区块链,模块化区块链生态系统的优势,并以Celestia、Dymension和Fuel等模块化区块链项目为例,探讨了它们解决可扩展性和部署问题的方案。模块化区块链架构提高了区块链的可扩展性和吞吐量,并提供了跨链互操作性和主权可扩展性。开发人员可以根据需要选择执行环境,并获得奖学金支持。该文对模块化区块链的应用案例进行了介绍,展示了其在区块链领域的潜力和前景。 ... [详细]
  • 项目运行环境配置及可行性分析
    本文介绍了项目运行环境配置的要求,包括Jdk1.8、Tomcat7.0、Mysql、HBuilderX等工具的使用。同时对项目的技术可行性、操作可行性、经济可行性、时间可行性和法律可行性进行了分析。通过对数据库的设计和功能模块的设计,确保系统的完整性和安全性。在系统登录、系统功能模块、管理员功能模块等方面进行了详细的介绍和展示。最后提供了JAVA毕设帮助、指导、源码分享和调试部署的服务。 ... [详细]
  • 本文介绍了常用的编辑器快捷键,包括快速转换编辑器、浏览选项卡、提取本地变量和方法、编辑器窗口最大化等功能。通过使用这些快捷键,可以提高编辑器的使用效率,减少复杂度,并提升代码的可测试性。 ... [详细]
  • 初探PLC 的ST 语言转换成C++ 的方法
    自动控制软件绕不开ST(StructureText)语言。它是IEC61131-3标准中唯一的一个高级语言。目前,大多数PLC产品支持ST ... [详细]
author-avatar
灬哭着说再见灬
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有