一文全面了解Android单元测试
一文全面了解Android单元测试
前言
众所周知,一个好的项目需要不断地打造,而一些有效的测试则是加速这一过程的利器。本篇博文将带你了解并逐步深入Android单元测试。
什么是单元测试?
单元测试就是针对类中的某一个方法进行验证是否正确的过程,单元就是指 独立的粒子,在Android和Java中大都是指方法。
为什么要进行单元测试?
使用单元测试可以提高开发效率,当项目随着迭代越来越大时,每一次编译、运行、打包、调试需要耗费的时间会随之上升,因此,使用单元测试可以不需这一步骤就可以对单个方法进行功能或逻辑测试。 同时,为了能测试每一个细分功能模块,需要将其相关代码抽成相应的方法封装起来,这也在一定程度上改善了代码的设计。因为是单个方法的测试,所以能更快地定位到bug。
单元测试case需要对这段业务逻辑进行验证。在验证的过程中,开发人员可以 深度了解业务流程,同时新人来了看一下项目单元测试就知道 哪个逻辑跑了多少函数,需要注意哪些边界——是的,单元测试做的好和文档一样 具备业务指导能力。
Android测试的分类
Android测试主要分为三个方面:
- 1)、单元测试(Junit4、Mockito、PowerMockito、Robolectric)
- 2)、UI测试(Espresso、UI Automator)
- 3)、压力测试(Monkey)
一、单元测试之基础Junit4
什么是Junit4?
Junit4是事实上的Java标准测试库,并且它是JUnit框架有史以来的最大改进,其主要目标便是利用Java5的Annotation特性简化测试用例的编写。
开始使用Junit4进行单元测试
1.Android Studio已经自动集成了Junit4测试框架,如下
1 | dependencies { |
2.Junit4框架使用时涉及到的重要注解如下
1 | 指明这是一个测试方法 (注解可以接受2个参数,一个是预期错误 |
此外,很多时候,因为某些原因(比如正式代码还没有实现等),我们可能想让JUnit忽略某些方法,让它在跑所有测试方法的时候不要跑这个测试方法。要达到这个目的也很简单,只需要在要被忽略的 测试方法前面加上@Ignore 就可以了
3.主要的测试方法——断言
1 | assertEquals(expected, actual) 判断2个值是否相等,相等则测试通过。 |
注意:上面的每一个方法,都有一个重载的方法,可以加一个String类型的参数,表示如果验证失败的话,将 用这个字符串作为失败的结果报告。
4.自定义Junit Rule——实现TestRule接口并重写apply方法
1 | public class JsonChaoRule implements TestRule { |
然后在想要的测试类中使用@Rule注解声明使用JsonChaoRule即可(注意 被@Rule注解的变量必须是final的):
1 |
|
5.开始上手,使用Junit4进行单元测试
- 1.编写测试类。
- 2.鼠标右键点击测试类,选择选择Go To->Test (或者使用快捷键Ctrl+Shift+T,此快捷键可 以在方法和测试方法之间来回切换)在Test/java/项目 测试文件夹/下自动生成测试模板。
- 3.使用断言(assertEqual、assertEqualArrayEquals等等)进行单元测试。
- 4.右键点击测试类,Run编写好的测试类。
6.使用Android Studio自带的Gradle脚本自动化单元测试
点击Android Studio中的 Gradle projects 下的 app/Tasks/verification/test 即可同时测试module下所有的测试类(案例),并在 module下的build/reports/tests/下 生成对应的 index.html测试报告。
7.对Junit4的总结:
- 优点:速度快,支持代码覆盖率等代码质量的检测工具,
- 缺点:无法单独对Android UI,一些类进行操作,与原生JAVA有一些差异。
可能涉及到的额外的概念:
打桩方法:使方法简单快速地返回一个有效的结果。
测试驱动开发:编写测试,实现功能使测试通过,然后不断地使用这种方式实现功能的快速迭代开发。
二、单元测试之基础Mockito
什么是Mockito?
Mockito 是美味的 Java 单元测试 Mock 框架,mock可以模拟各种各样的对象,从而代替真正的对象做出希望的响应。
开始使用Mockito进行单元测试
1.在build.gradle里面添加Mcokito的依赖
1 | testImplementation 'org.mockito:mockito-core:2.7.1' |
2.使用mock()方法模拟对象
1 | Person mPerson = mock(Person.class); |
能量补充站(-vov-)
在JUnit框架下,case(即每一个测试点,带@Test注解的那个函数)也是个函数,直接调用这个函数就不是case,和case是无关的,两者并不会相互影响,可以直接调用以减少重复代码。 单元测试不应该对某一个条件过度耦合,因此,需要用mock解除耦合, 直接mock出网络请求得到的数据,单独验证页面对数据的响应。
3.验证方法的调用,指定方法的返回值,或者执行特定的动作
1 | when(iMathUtils.sum(1, 1)).thenReturn(2); |
4.使用Mockito后的思考
简单的测试会使整体的代码更简单,更可读、更可维护。如果你 不能把测试写的很简单,那么请在测试时重构你的代码。
- 优点:丰富强大的方式验证“模仿对象”的互动或验证发生的某些行为
- 缺点:Mockito框架不支持mock匿名类、final类、static方法、private方法。
虽然,static方法可以使用wrapper静态类的方式实现mockito的单元测试,但是,毕竟过于繁琐,因此,PowerMockito由此而来。
三、拯救Mockito于水深火热的PowerMockito
什么是PowerMockito?
PowerMockito是一个扩展了Mockito的具有更强大功能的单元测试框架,它支持mock匿名类、final类、static方法、private方法
开始PowerMockito之旅
1.在build.gradle里面添加Mcokito的依赖
1 | testImplementation 'org.powermock:powermock-module-junit4:1.6.5' |
2.用PowerMockito来模拟对象
1 | //使用PowerMock须加注解@PrepareForTest和@RunWith(PowerMockRunner.class)(@PrepareForTest()里写的 |
3.使用PowerMockRule来代替@RunWith(PowerMockRunner.class)的方式,需要多添加以下依赖:
1 | testImplementation "org.powermock:powermock-module-junit4-rule:1.7.4" |
使用示例如下:
1 |
|
4.使用Parameterized来进行参数化测试:
通过注解@Parameterized.parameters提供一系列数据给构造器中的构造参数 或给 被注解@Parameterized.parameter注解的public全局变量
1 | RunWith(Parameterized.class) |
四、能在Java单元测试里面执行Android代码的Robolectric
什么是Robolectric?
Robolectric通过 一套能运行在JVM上的Android代码,解决了在Java单元测试中很难进行Android单元测试的痛点。
进入Roboletric的领地
1.在build.gradle里面添加Robolectric的依赖
1 | //Robolectric核心 |
2.Robolectric常用用法
首先给指定的测试类上面进行配置
1 |
|
下面是一些常用用法:
1 | //当Robolectric.setupActivity()方法返回的时候, |
自定义Shadow类:
1 |
|
注意:异步测试出现一些问题(比如改变一些编码习惯,比如回调函数不能写成匿名内部类对象,需要定义一个全局变量,并破坏其封装性,即提供一个get方法,供UT调用),解决方案 使用Mockito来结合进行测试,将异步转为同步。
3.Robolectric的优缺点
- 优点:支持大部分Android平台依赖类底层的引用与模拟。
- 缺点:异步测试有些问题,需要结合一些框架来配合完成更多功能。
五、单元测试覆盖率报告生成之jacoco
什么是Jacoco
Jacoco的全称为Java Code Coverage(Java代码覆盖率),可以 生成java的单元测试代码覆盖率报告。
加入Jacoco到你的单元测试大家族
在应用Module下加入jacoco.gradle自定义脚本,app.gradle apply from它,同步,即可看到在app的Task下生成了Report目录,Report目录 下生成了JacocoTestReport任务。
1 | apply plugin: 'jacoco' |
在Gradle构建板块 Gradle.projects 下的 app/Task/verification 下,其中 testDebugUnitTest 构建任务会生成单元测试结果报告,包 含xml及html 格式,分别对应 test-results和reports 文件夹;jacocoTestReport任务会生成单元测试覆盖率报告,结果存放在jacoco和JacocoReport文件夹。
生成的JacocoReport文件夹下的index.html即对应的单元测试覆盖率报告,用浏览器打开后,可以看到覆盖情况被不同的颜色标识出来,其中 绿色表示代码被单元测试覆盖到,黄色表示部分覆盖,红色则表示完全没有覆盖到。
六、单元测试的流程
要验证程序正确性,必然要给出所有可能的条件(极限编程),并验证其行为或结果,才算是100%覆盖条件。实际项目中,验证 一般条件 和 边界条件 就OK了。
在实际项目中, 单元测试对象与页面是一对一的,并不建议跨页面,这样的单元测试耦合太大,维护困难。 需要写完后,看覆盖率,找出单元测试中没有覆盖到的函数分支条件等,然后继续补充单元测试case列表,并在单元测试工程代码中补上case。 直到规划的 页面中所有逻辑的重要分支、边界条件都被覆盖,该项目的单元测试结束。
建议(-ovo-)~
可以从公司项目 小规模使用,形成 自己的单元测试风格 后,就可更大范围地推广了。
公钟号同名,欢迎关注,关注后回复 Framework,我将分享给你一份我这两年持续总结、细化、沉淀出来的 Framework 体系化精品面试题,里面很多的核心题答案在面试的压力下,经过了反复的校正与升华,含金量极高~