【测试左移专栏】用Powermock和Mockito来做安卓单元测试

栏目: 编程工具 · 发布时间: 7年前

内容简介:【测试左移专栏】用Powermock和Mockito来做安卓单元测试

一、单元测试及Android单元测试简介

惯例,先简单介绍下理论知识,懂得可以跳过。

1、单元测试定义和特性

单测定义:

在计算机编程中,单元测试(Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。

程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

单测特性:

截取下《单元测试的艺术》一书中的优秀的单元测试特性,牢记!

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

2、Android单元测试

顾名思义,是在Android系统下进行的单元测试。

业界上已经有很多 工具 可以支持做Android系统下的单元测试,主要分为两大类:

(1)Instrumentation

通过Android系统的Instrumentation测试框架,我们可以编写测试代码,并且打包成APK,运行在Android手机上。

优点: 逼真;

缺点: 很慢;

代表框架:Junit,Espresso。

(2)Junit / Mock

通过Junit,以及第三方测试框架,我们可以编写测试代码,生成class文件,直接运行在JVM虚拟机中。

优点: 很快,使用简单,方便;

缺点: 不够逼真,比如有些硬件相关的问题,无法通过这些测试出来;

代表框架: Junit,Robolectric, Mockito, Powermock。

Robolectric:一个单元测试框架,可以清除Android SDK(通过shadow技术),以便您可以测试驱动Android应用程序的开发,测试JVM内部运行,用例执行速度很快。

其官网地址:http://Robolectric.org/。

Espresso:一种简洁,美观,可靠的Android UI测试框架。

其API地址:https://developer.Android.com/reference/Android/support/test/Espresso/Espresso.html。

Mockito:一个针对 Java 的单元测试模拟框架,它与 EasyMock 和 jMock 很相似,都是为了简化单元测试过程中测试上下文 ( 或者称之为测试驱动函数以及桩函数 ) 的搭建而开发的工具。

其中文开发文档:http://www.devtf.cn/?p=1315。

Powermock:是在 EasyMock 以及 Mockito 基础上的扩展,通过定制类加载器等技术,实现了之前提到的所有Mockito不能模拟的功能,比如静态函数、构造函数、私有函数、Final 函数以及系统函数的模拟。

二、Google官方MVP架构

在熟悉单元测试框架前,首先需要学习了下Google官方推荐Android的MVP项目架构,好的框架单元测试也比较好开展。

其推荐的项目中MVP各层所使用的单元测试框架如下图所示:

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

其MVP测试架构图总结如下:

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

项目代码有兴趣学习的同学可以去自行下载去学习,学习这种优秀代码是最快的方式。

View层:

职责:MVP模式下,View本身该做的事情都能做了,比如UI布局,数据渲染,点击按钮交互等等。

测试方式:以正常小QA的测试思维方法,就可以来定义这一层的测试方式,测试过程中需要真机或模拟器,并做真实的操作。

测试选型:依赖于Android环境,用谷歌强大的Espresso+AndroidJunitRunner,Espresso用于模拟和验证各种各样的UI操作,代码存放于AndroidTest中。

Presenter层:

职责:这一层是拉皮条的,负责M和V层的对接,所以有较少的处理输入输出的机会,他只用来控制逻辑,去调用相应的Model和View的逻辑。

测试选型:他的职责决定了他很少去断言输入输出,测试逻辑覆盖的路径是否正确即可,因此他与Android环境无关,用Junit+Mockito测试即可,代码存放于test中。

Model层:

职责:负责数据的存取,数据可能来自于网络、数据库和内存。

数据库增删改查:需测试数据存取的准确性,依赖Android环境进行测试,因此使用AndroidJunitRunner,代码存放于AndroidTest中。

网络请求:不测试真实的网络请求,但提供了Fake供其他层调用测试。

封装的门面类:决定了数据的来源和去向是来自于本地数据库 or 网络 or 内存,此为真正对其他层暴露的Model类。此类不做数据准确性的验证,只做mock测试,验证覆盖路径。UT选型Junit+Mockito,代码存放于test中。

MVP各个模块通信方式如下:

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

除了MVP,还有一种MVC的方式。

MVC的全称为Model-View-Controller,即模型-视图-控制器。

Model:处理数据和业务逻辑等。

View:显示界面,展示结果等。

Controller:控制流程,处理交互。

MVC各个模块通信方式如下:

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

MVC和MVP区别:

在MVC模式中,View和Model可以直接交互;在MVP模式中,View和Model模块不能直接交互,View通过Presenter与Model间接交互。

在MVC中,Controller是基于行为的,可以被多个View共享,可以负责决定显示哪个View;在MVP中View和Presenter是一对一或这一对多的,并且Presenter和View是通过接口交互的。

三、单元测试环境一些基本的准备工作

1、新建一个标准的Android Studio工程

新建一个andriod Studio工程,这个就不详细说明了,网上有很多教程。

成功后src目录下就出现AndroidTest和test下目录。

2、源码和其他工程目录搬迁移植

将源码目录全部放在src/main/java下(适合老业务改造)

如果源码目录指定不对,需要修改build.Gradle的sourceSets配置。

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

3、增加工具框架依赖

在dependencies下增加工具框架的引用。

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

注:如果用到什么框架就将框架引用进来即可,但有些工具主要版本号的相互搭配,不匹配可能会出现错误。

网上有一个PowerMock对Mockito的版本对应关系:

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

作者使用的是下面红色的组合,请根据实际情况匹配。

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

4、增加Jacoco覆盖率

增加Jacoco的插件:

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

指定版本号和报告目录:

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

指定源码目录.

自定义Jacoco报告规则task:

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

上面一切准备完毕后,配置好代码,Gradle就可以正常同步加载了。

如果你的Android Studio的Gradle Sync同步成功,那么恭喜你单测环境基本OK了,依赖库基本也已经下载完毕,下面可以愉快的开始着手代码编写了。

可能有的公司需要网络代理,那这个需要根据具体情况在Gradle中配置了。

四、编写AndroidTest下的单测用例

UI层的单元测试只简单介绍一下,作者实际编写单元测试的时候,UI部分的单元测试用例也是放在了test目录下一起写的(PowerMock模拟的),运行不需要手机或模拟器,执行速度比较快。

虽然没有在实际项目中大量使用,但也将当初的尝试简单介绍一下,供参考。

UI的Instrumentation用例可以选取Espresso。

在AndroidTest目录下新建一个测试类。

比如我们测试一个这样的单测用例:测试更新页的点击更新所有,用户页面会弹出一个toast确认的弹框。

用例编写如下:

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

手机连上电脑,选中用例鼠标右键run就可以运行看结果了。

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

五、编写test下的单元测试用例

首先介绍下单测工具框架选取的过程。

1、选取合适的测试框架

作者开始在业务中尝试使用Robolectric测试框架,初心主要在于他的特性:

Robolectric Test-Drive Your Android Code Running tests on an Android emulator or device is slow! Building, deploying, and launching the app often takes a minute or more. That’s no way to do TDD. There must be a better way.

Wouldn’t it be nice to run your Android tests directly from inside your IDE? Perhaps you’ve tried, and been thwarted by the dreaded java.lang.RuntimeException: Stub!?

它不需要Run你的模拟器,直接在jvm上运行你的测试代码,能在短时间之内快速验证,通过体验之后,它确实非常高效,编写测试代码反而加速了开发效率。

另外被它强大的Shadows方式所吸引,可以完全实现自定义方式。

但在实际使用的过程中遇到了不少的坑,比如:

Robolectric版本和SDK版本强依赖。

compileSdkVersion 23的不能使用Robolectric:3.0的版本,只能使用Robolectric:3.2.2以上的。为什么会有这种强依赖,是因为Robolectric会shadow大部分Android的代码,会有很多shadow的类,也就会随sdk版本的变化而变化。

Robolectric首次启动下载maven相关的依赖失败。

即使我们在开发网下设置了代理,开通外网权限,首次启动还会去下载相关依赖,结果是下载失败,这个是由于Robolectric本身代码里的逻辑,我们不能通过网络代理的方式解决。

唯一的办法只能一个一个手工的下载后丢到你的.m2\repository\org\Robolectric目录下,让Robolectric找到其所依赖的jar包,不需要在去下载,如下:

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

如果在build.Gradle重新指定Robolectric的版本,那么这些需要的版本还要手工下载一遍。

Robolectric运行报TinkerRuntimeException: Tinker Exception:onCreate method not found

业务使用了Tinker多包加载架构,运行出现上面的异常。

解决方法:

RealAstApp里面人为增加oncreate方法

@Override

public void onCreate() {

}

这样修改代码其实是有点犯忌的,但只改这一处还勉强可以接受,下面的就不能接受了。

Robolectric运行在自定义的控件时有时会出现xml解析异常。

跟踪解决了几个,发现要修改的地方比较多,这里省略一万字的修改记录。

除了改动点比较多,也可能后续会出现更多的潜在错误。

违背上面的单元测试特性之运行稳定,衡量再三,还是决定放弃Robolectric了,另寻它径。

这里也声明下,Robolectric工具还是很优秀的,它的解决思路很清晰,所有调用到Android相关的都会转移到其shadow类,这样就可以完全脱离Android的限制,只是由于业务的特殊性才暂时不用。

于是又开始研究Espresso,见上面的(编写AndroidTest单元测试用例)。

使用过程中总体感觉Espresso功能比较强大,只要合理的使用其提供的api和matches规则,常用的UI逻辑基本都可以模拟,但唯一不爽的就是每次都要连接手机或者模拟器才能运行,Run的过程中,首先会打包,部署到手机上,然后再开始一个一个运行测试用例,好处是手机上的表现很直观,但这样调试和运行速度是真心的慢。

违背上面的单元测试特性之运行速度快,建议放弃。

尝试使用Junit、Mockito和Powermock来编写MVP三层的单元测试用例,在经过一阵探索后,MVP三层的逻辑基本都可以通过Mockito和Powermock来模拟出来,运行起来关键是速度快,速度快,速度快,好的地方说三遍。

上面的单元测试特性也基本都能满足,最终决定使用Junit、Mockito和Powermock这个框架组合来进行我们的单元测试用例设计和编写。

2、选取被测模块和熟悉被测模块的代码逻辑

在单元测试前要对被测模块有个大致的代码逻辑熟悉,对代码的深入可以边写边熟悉。

3、PowerMock知识点掌握

单测用例编写过程中,熟练程度一部分完全取决于对单测工具框架的了解程度,这块没捷径可走,必须要掌握清楚明白,简单列一下其知识点,具体还是需要自己去搜索资料掌握的。

(1)PowerMock注解@RunWith与@PrepareForTest的使用;

(2)测试或模拟static方法;

(3)测试或模拟返回void的静态方法;

(4)PowerMockito.doNothing与PowerMockito.doThrow的使用;

(5)如何验证方法调用;

(6)如何验证调用次数的方法;

(7)测试或模拟final类或方法;

(8)测试或模拟构造方法;

(9)如何做参数匹配;

(10)Answer接口的使用;

(11)如何使用spy进行部分模拟;

(12)如何测试或模拟私有方法;

(13)@Before和@Test的作用;

(14)如何给私有的字段赋值;

(15)如何模拟异常。

4、设计单元测试用例

需要写单测case列表

在我们的项目中,单元测试对象建议和类相对应,这样的单元测试结果比较直观。单元测试分析被测类的业务逻辑,这里的逻辑不仅仅包括界面元素的展示以及控件组件的行为,还包括代码的处理逻辑。然后可以创建单元测试case列表,列表用于纪录项目中单元测试的范围,便于单元测试的管理以及新人了解业务流程,列表中记录单元测试对象的页面,对象中的case逻辑以及名称等,测试或开发工程师可以根据这个列表开始写单元测试代码。

用覆盖率来校验单测用例是否完备

单元测试是工程师代码级别的质量保证工程,上述流程并不能完全覆盖重要的业务逻辑以及边界条件,因此,需要写完后,看覆盖率,找出单元测试中没有覆盖到的函数分支条件等,然后继续补充单元测试case列表,并在单元测试工程代码中补上case。

直到被测类所有逻辑的重要分支、边界条件都被覆盖,才认为该类的单元测试结束。

另外觉得复用或通用的逻辑建议做成工具类,直接复用。

整理了一个case的单测流程图,供参考:

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

5、公共的可复用的抽离出成工具类

将一些常用的场景抽象出工具mock类,如BundleMock、HandlerMock、IntentMock、MainThreadHandler、ParcelMock等等,这样提供给单测直接调用即可,不用在重复造车轮。

6、几种场景的单元测试用例案例

单元测试用例设计,格式可以自己灵活去定义,另外也可以在代码中已Javadoc的方式添加单元测试用例内容,输入、输出、断言几点明确就可以了。

我们把一部分项目常用的场景通过mock实现后,剩下的基本都是工作量的问题了。

目前业务代码逻辑场景的模拟做了如下:

(1)请求的模拟及回调onRequestSuccessed和onRequestFailed的模拟;

(2)页面inflate加载场景模拟;

(3)页面findViewById加载场景模拟;

(4)控件onclick场景模拟;

(5)数据回调场景模拟;

(6)主线程handler场景模拟;

(7)序列化的模拟;

(8)intent的模拟;

(9)其他等等。

这部分的模拟代码就不贴上来了,有点多,需要可以线下交流。

7、单测类的编写经验

(1)mock对象可以被整个类的测试方法共用的,mock时统一放到@Before里init;

(2)mock对象仅供单个单测用例使用的,mock时可以直接放到单测用例里;

(3)能抽象出来的mock对象,建议做成工具类调用;

(4)单测用例一定要有断言,且断言准确,这样才能保证单测用例的有效性;

(5)不要怕麻烦,开始都会感觉很难,写多了熟练了就好了。

8、debug调试

执行时候如何出现一堆黄色的PASSED,心里当然感觉爽了。

但在单元测试编写运行中难免会出现各种异常错误,mock时出现空指针的场景会比较多,这时候我们就需要用debug调试方式。

【测试左移专栏】用Powermock和Mockito来做安卓单元测试

然后设置断点,通过F8逐步跟踪下去吧,找出单测用例的编写的问题所在。

9、生成覆盖率报告

在Android Studio的Terminal中输入Gradlew JacocoTestReport后,单元测试开始运行,无错误结束后就会在指定的报告生成目录下看到覆盖率结果了。

通过覆盖率结果,查看到单测case覆盖情况,根据情况补充或修改单测用例,加大覆盖率结果的提升,单测是有望达到100%覆盖的。

单测过程中可能会出现某些类的覆盖率结果为0的,但实际上应该有覆盖率的,这可能是由于一些页面单测场景下被测类在@PrepareForTest中声明了,导致这些类的覆盖率为0。以前作者也介绍过Jacoco的原理,其是修改class字节码文件插桩的,但再经过PrepareForTest这种指定后,PowerMock也会修改class的字节码,这样就把Jacoco的插桩冲掉了,导致覆盖率为0,这部分我们可以通过自己写脚本的方式来算覆盖率,然后在和Jacoco的覆盖率相叠加算出总的覆盖率。

六、做单测的意义

现在各个项目的代码量都比较庞大,全部进行单测覆盖,工作量消耗是非常巨大的。

并且产出和收益也不一定成正比例。

其实我们做单测和做系统测试的出发点都是一样的,提升项目的总体质量。

两点实施方式:

1、对于开发久,稳定的功能,单测的出发点为系统功能测试的互补。

单测的着重点在功能测试难覆盖的地方,通过单测发现功能测试难发现的问题及代码潜在的问题。

2、对应刚开发,新功能,如果有时间和人力的话,可以考虑单测全覆盖。

尽量在开发编码时并行实施,或者推动开发自己写单测。

最后有一个话题有机会大家可以一起讨论下:

单测的投入和产出如何来平衡?

版权所属,禁止转载


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

上瘾

上瘾

[美] 尼尔·埃亚尔、[美] 瑞安·胡佛 / 钟莉婷、杨晓红 / 中信出版集团 / 2017-5 / 49.00元

——为什么我们会习惯性地点开某个App? ——这种使用习惯到底是如何养成的? ——为什么有些产品能让我们戒不掉,而其他的产品却不行? ——是否有什么秘诀能让用户对你的产品形成使用习惯,欲罢不能? 《上瘾》揭示了很多让用户形成使用习惯,甚至“上瘾”的互联网产品服务背后的基 本设计原理,告诉你怎样打造一款让用户欲罢不能的产品。作者根据自己多年的研究、咨询及实际经验,提出了新颖而......一起来看看 《上瘾》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码