iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >深入探讨Unit Testing in Android
  • 785
分享到

深入探讨Unit Testing in Android

testingAndroid 2022-06-06 10:06:42 785人浏览 独家记忆
摘要

1. Testing for ContentProvider在你开始为Provider写Case之前,应该仔细读一读SDK文档中关于Provider测试的说明。但是光读那些说明

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:
代码如下:
    @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<FeedProvider> {
    private ContentResolver mContentResolver;
    public DemoProviderTest() {
        this(FeedProvider.class, AUTHORITY);
    }
    public DemoProviderTest(Class<FeedProvider> providerClass,
            String providerAuthority) {
        super(providerClass, providerAuthority);
    }
    @Override
    public void setUp() throws Exception {
        super.setUp();
      & nbsp; 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<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);
    }
    @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的唯一优势!现的社会是一分钱一分货,便宜自然就没好货!


--结束END--

本文标题: 深入探讨Unit Testing in Android

本文链接: https://www.lsjlt.com/news/27769.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 深入探讨PHP SDK的定义
    PHP SDK(Software Development Kit,软件开发工具包)是一种用于简化开发人员在特定领域或平台上开发应用程序的工具包。它提供了一系列功能和接口,使开发人员能够...
    99+
    2024-03-11
    php sdk 探讨 api调用
  • 深入探讨JavaScript用作什么
    随着计算机和互联网的发展,编程已成为一项非常重要的技能。JavaScript作为一种广泛使用的编程语言,被广泛应用于各种网站和应用程序中。本文将深入探讨JavaScript用作什么,并探讨它的一些关键特性和功能。网页交互JavaScript...
    99+
    2023-05-14
  • 深入探讨Golang的除法运算
    Golang是一种高效、快速、强大和可靠的编程语言,使用起来十分方便和容易。随着Golang语言的发展和应用范围的扩大,越来越多的用户开始对其除法运算的性能、精度和可靠性提出了更高的要求。在本文中,我们将深入探讨Golang的除法运算,包括...
    99+
    2023-05-14
    go语言 Golang
  • 深入探讨JavaScript中的async函数
    说白了:await就相当于 then 方法的第一个回调函数,只返回成功的值,失败的值需要 try...catch来捕获。async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调...
    99+
    2022-11-22
    async await javascript
  • 深入探讨 Golang 接口的实现
    Golang(又称Go语言)是一门现代化的编程语言,它是由 Google 设计和维护的。Golang 是一种静态语言,它通过强类型和严格类型检查的方式来提高代码的可维护性和健壮性。其中一个最有趣的特性就是接口,本文将会深入探讨 Golang...
    99+
    2023-05-14
    Golang go语言 接口
  • 深入探讨golang的底层实现
    Golang是一种高效、现代化的编程语言,以其快速、简单和安全的开发模式,在近年来越来越受到开发者的欢迎。Go语言不仅支持多线程,还具有良好的并发开发能力,同时它也是一种非常底层的语言,这使得Go语言的底层实现得到了广泛关注。考虑到语言设计...
    99+
    2023-05-14
  • 深入探讨:Django框架是什么?
    Django框架是一种用于Web应用程序的Python框架,它提供了一个简单而强大的方式来创建Web应用程序。事实上,Django已经成为当前最受欢迎的Python Web开发框架之一,也成为很多公司的首选,包括Instagra...
    99+
    2024-01-19
    框架 django 探讨
  • 深入探讨PHP的标准标记
    在Web开发中,PHP是一种被广泛应用的编程语言,它的标准标记是PHP代码中的特殊标记符号,用于标识PHP代码的开始和结束。深入了解PHP的标准标记是非常重要的,因为它们直接影响着PH...
    99+
    2024-04-02
  • 深入探讨uniapp的icon放在哪
    随着移动端应用市场的日益发展,开发者们越来越注重应用的用户体验和美观程度。除了实现功能外,如何设计出更具有吸引力的应用界面也成为了开发者们需要思考的问题。而其中,icon设计的重要性不言而喻。在uniapp中,如何放置icon,也是一些初学...
    99+
    2023-05-14
  • 深入探讨:什么是Go语言?
    Go语言,也被称为Golang,是一种由Google开发的编程语言。它于2007年正式发布,旨在解决一些其他编程语言存在的问题,并提供更高效的解决方案。Go语言被设计成一种简洁、快速、...
    99+
    2024-02-23
    go语言 探讨 深入 标准库
  • 深入探讨golang无法翻页问题
    随着Golang的流行和应用越来越广,开发者们逐渐意识到Golang这门语言也有一些限制和局限性。其中之一就是Golang在进行分页操作时的表现,常常会出现无法翻页的情况。本文将深入探讨这个问题,并提供一些解决方案。为什么会出现无法翻页的情...
    99+
    2023-05-14
  • 深入探讨Golang中的泛型概念
    深入探索 Golang 中的泛型概念 前言 Golang 1.18 中引入的泛型是一种强大的语言特性,它允许您在代码中使用类型变量,从而提高代码的可重用性和可维护性。在这篇文章中,我们...
    99+
    2024-04-03
    golang 泛型 键值对
  • 深入探讨Go语言最新特性
    在过去的几年里,Go语言一直在不断发展和演进,每个版本都会带来一些新的特性和改进。本文将深入探讨Go语言最新的一些特性,以及提供具体的代码示例来帮助读者更好地理解和应用这些特性。 并发...
    99+
    2024-03-09
    深入探讨 最新特性 go语言特性 go语言 标准库
  • 深入探讨Vue3的响应式机制
    什么是响应式?Vue是怎么实现响应式的?下面本篇文章带大家深入了解下Vue3的响应式原理,希望对大家有所帮助!Vue这个框架相信大家都不陌生了,提到Vue我相信面试官首先会问的问题之一就是Vue的响应式原理是如何实现的,之前也写过一篇Vue...
    99+
    2023-05-14
    响应式 Vue.js 前端
  • 深入探讨vue适合web开发吗
    Vue.js 是当下最火热的 JavaScript 前端框架之一,它提供了一种简单的方式来构建用户界面。Vue.js 非常容易学习,而且在构建单页面应用程序时非常有用。但是,对于 Web 开发来说,Vue.js 是否适合呢?我们来深入探讨一...
    99+
    2023-05-14
  • 如何深入探讨、理解Java的CLASSPATH
    这篇文章给大家介绍如何深入探讨、理解Java的CLASSPATH,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。从表面上看,Java的classpath(类路径)很简单,但一直以来它都是一个产生问题和混乱的根源。本文介绍...
    99+
    2023-06-03
  • 深入探讨JavaScript中的内存管理
    内存管理是编程语言的基本能力,JavaScript 中的内存管理是通过 V8 完成的。V8 的实现遵循 ECMA-262 规范,而规范中没有阐述内存布局以及内存管理相关信息,所以它的原理取决于解释器的实现。唯一肯定的是不管任何编程语言,内存...
    99+
    2023-05-14
    前端 JavaScript
  • 深入探讨SQL的意义与功能
    深入解析SQL的含义与作用引言随着现代信息技术的快速发展,海量数据的产生和存储已经成为一种常态。而要处理这些海量数据,我们需要一种高效、强大、灵活的工具。结构化查询语言(Structured Query Language,简称SQL)作为一...
    99+
    2023-12-28
    解析 (Parsing) SQL (Structured Query Language) 含义与作用 (Meaning
  • 深入探讨PHP的Socket通信能力
    深入探讨PHP的Socket通信能力 概述:Socket(套接字)是计算机网络中一种基础的通信方式,通过它可以实现不同计算机之间的数据传输。在PHP中,通过Socket扩展库,我们可以...
    99+
    2024-03-07
    php 通信 socket
  • 深入探讨Angular8和Vue间的区别
    AngularJS8和Vue是两个非常流行的JavaScript框架,在前端开发中广泛使用。虽然它们的主要目标都是使开发人员更轻松地创建交互性Web应用程序,但它们在设计思路、功能、使用方式等方面还是存在很大的差异。本文将深入探讨Angul...
    99+
    2023-05-14
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作