内容简介:【测试左移专栏】用Powermock和Mockito来做安卓单元测试
一、单元测试及Android单元测试简介
惯例,先简单介绍下理论知识,懂得可以跳过。
1、单元测试定义和特性
单测定义:
在计算机编程中,单元测试(Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。
程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
单测特性:
截取下《单元测试的艺术》一书中的优秀的单元测试特性,牢记!
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各层所使用的单元测试框架如下图所示:
其MVP测试架构图总结如下:
项目代码有兴趣学习的同学可以去自行下载去学习,学习这种优秀代码是最快的方式。
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各个模块通信方式如下:
除了MVP,还有一种MVC的方式。
MVC的全称为Model-View-Controller,即模型-视图-控制器。
Model:处理数据和业务逻辑等。
View:显示界面,展示结果等。
Controller:控制流程,处理交互。
MVC各个模块通信方式如下:
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配置。
3、增加工具框架依赖
在dependencies下增加工具框架的引用。
注:如果用到什么框架就将框架引用进来即可,但有些工具主要版本号的相互搭配,不匹配可能会出现错误。
网上有一个PowerMock对Mockito的版本对应关系:
作者使用的是下面红色的组合,请根据实际情况匹配。
4、增加Jacoco覆盖率
增加Jacoco的插件:
指定版本号和报告目录:
指定源码目录.
自定义Jacoco报告规则task:
上面一切准备完毕后,配置好代码,Gradle就可以正常同步加载了。
如果你的Android Studio的Gradle Sync同步成功,那么恭喜你单测环境基本OK了,依赖库基本也已经下载完毕,下面可以愉快的开始着手代码编写了。
可能有的公司需要网络代理,那这个需要根据具体情况在Gradle中配置了。
四、编写AndroidTest下的单测用例
UI层的单元测试只简单介绍一下,作者实际编写单元测试的时候,UI部分的单元测试用例也是放在了test目录下一起写的(PowerMock模拟的),运行不需要手机或模拟器,执行速度比较快。
虽然没有在实际项目中大量使用,但也将当初的尝试简单介绍一下,供参考。
UI的Instrumentation用例可以选取Espresso。
在AndroidTest目录下新建一个测试类。
比如我们测试一个这样的单测用例:测试更新页的点击更新所有,用户页面会弹出一个toast确认的弹框。
用例编写如下:
手机连上电脑,选中用例鼠标右键run就可以运行看结果了。
五、编写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包,不需要在去下载,如下:
如果在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的单测流程图,供参考:
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调试方式。
然后设置断点,通过F8逐步跟踪下去吧,找出单测用例的编写的问题所在。
9、生成覆盖率报告
在Android Studio的Terminal中输入Gradlew JacocoTestReport后,单元测试开始运行,无错误结束后就会在指定的报告生成目录下看到覆盖率结果了。
通过覆盖率结果,查看到单测case覆盖情况,根据情况补充或修改单测用例,加大覆盖率结果的提升,单测是有望达到100%覆盖的。
单测过程中可能会出现某些类的覆盖率结果为0的,但实际上应该有覆盖率的,这可能是由于一些页面单测场景下被测类在@PrepareForTest中声明了,导致这些类的覆盖率为0。以前作者也介绍过Jacoco的原理,其是修改class字节码文件插桩的,但再经过PrepareForTest这种指定后,PowerMock也会修改class的字节码,这样就把Jacoco的插桩冲掉了,导致覆盖率为0,这部分我们可以通过自己写脚本的方式来算覆盖率,然后在和Jacoco的覆盖率相叠加算出总的覆盖率。
六、做单测的意义
现在各个项目的代码量都比较庞大,全部进行单测覆盖,工作量消耗是非常巨大的。
并且产出和收益也不一定成正比例。
其实我们做单测和做系统测试的出发点都是一样的,提升项目的总体质量。
两点实施方式:
1、对于开发久,稳定的功能,单测的出发点为系统功能测试的互补。
单测的着重点在功能测试难覆盖的地方,通过单测发现功能测试难发现的问题及代码潜在的问题。
2、对应刚开发,新功能,如果有时间和人力的话,可以考虑单测全覆盖。
尽量在开发编码时并行实施,或者推动开发自己写单测。
最后有一个话题有机会大家可以一起讨论下:
单测的投入和产出如何来平衡?
版权所属,禁止转载
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Vue 应用单元测试的策略与实践 02 - 单元测试基础
- Vue 应用单元测试的策略与实践 04 - Vuex 单元测试
- Vue 应用单元测试的策略与实践 03 - Vue 组件单元测试
- Angular单元测试系列-Component、Directive、Pipe 以及Service单元测试
- 单元测试,只是测试吗?
- 单元测试和集成测试业务
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。