转载请作明出处:juejin.im/post/5b4381…
==》完整项目单元测试学习案例
众所周知,一个好的项目需要不断地打造,而一些有效的测试则是加速这一过程的利器。本篇博文将带你了解并逐步深入Android单元测试。
单元测试就是针对类中的某一个方法进行验证是否正确的过程,单元就是指独立的粒子,在Android和Java中大都是指方法。
使用单元测试可以提高开发效率,当项目随着迭代越来越大时,每一次编译、运行、打包、调试需要耗费的时间会随之上升,因此,使用单元测试可以不需这一步骤就可以对单个方法进行功能或逻辑测试。 同时,为了能测试每一个细分功能模块,需要将其相关代码抽成相应的方法封装起来,这也在一定程度上改善了代码的设计。因为是单个方法的测试,所以能更快地定位到bug。
单元测试case需要对这段业务逻辑进行验证。在验证的过程中,开发人员可以深度了解业务流程,同时新人来了看一下项目单元测试就知道哪个逻辑跑了多少函数,需要注意哪些边界——是的,单元测试做的好和文档一样具备业务指导能力。
Android测试主要分为三个方面:
单元测试(Junit4、Mockito、PowerMockito、Robolectric) UI测试(Espresso、UI Automator) 压力测试(Monkey) 复制代码Junit4是事实上的Java标准测试库,并且它是JUnit框架有史以来的最大改进,其主要目标便是利用Java5的Annotation特性简化测试用例的编写。
此外,很多时候,因为某些原因(比如正式代码还没有实现等),我们可能想让JUnit忽略某些方法,让它在跑所有测试方法的时候不要跑这个测试方法。要达到这个目的也很简单,只需要在要被忽略的测试方法前面加上@Ignore就可以了
注意:上面的每一个方法,都有一个重载的方法,可以加一个String类型的参数,表示如果验证失败的话,将用这个字符串作为失败的结果报告。
然后在想要的测试类中使用@Rule注解声明使用JsonChaoRule即可(注意被@Rule注解的变量必须是final的):
@Rule public final JsonChaoRule repeatRule = new JsonChaoRule(); 复制代码点击Android Studio中的Gradle projects下的app/Tasks/verification/test即可同时测试module下所有的测试类(案例),并在module下的build/reports/tests/下生成对应的index.html测试报告。
可能涉及到的额外的概念:
打桩方法:使方法简单快速地返回一个有效的结果。
测试驱动开发:编写测试,实现功能使测试通过,然后不断地使用这种方式实现功能的快速迭代开发。
Mockito 是美味的 Java 单元测试 Mock 框架,mock可以模拟各种各样的对象,从而代替真正的对象做出希望的响应。
在JUnit框架下,case(带@Test注解的那个函数)也是个函数,直接调用这个函数就不是case,和case是无关的,两者并不会相互影响,可以直接调用以减少重复代码。单元测试不应该对某一个条件过度耦合,因此,需要用mock解除耦合,直接mock出网络请求得到的数据,单独验证页面对数据的响应。
简单的测试会使整体的代码更简单,更可读、更可维护。如果你不能把测试写的很简单,那么请在测试时重构你的代码。
优点:丰富强大的方式验证“模仿对象”的互动或验证发生的某些行为 缺点:Mockito框架不支持mock匿名类、final类、static方法、private方法。 复制代码虽然,static方法可以使用wrapper静态类的方式实现mockito的单元测试,但是,毕竟过于繁琐,因此,PowerMockito由此而来。
PowerMockito是一个扩展了Mockito的具有更强大功能的单元测试框架,它支持mock匿名类、final类、static方法、private方法
使用示例如下:
@Rule public PowerMockRule mPowerMockRule = new PowerMockRule(); 复制代码通过注解@Parameterized.parameters提供一系列数据给构造器中的构造参数或给被注解@Parameterized.parameter注解的public全局变量
RunWith(Parameterized.class) public class ParameterizedTest { private int num; private boolean truth; public ParameterizedTest(int num, boolean truth) { this.num = num; this.truth = truth; } //被此注解注解的方法将把返回的列表数据中的元素对应注入到测试类 //的构造函数ParameterizedTest(int num, boolean truth)中 @Parameterized.Parameters public static Collection providerTruth() { return Arrays.asList(new Object[][]{ {0, true}, {1, false}, {2, true}, {3, false}, {4, true}, {5, false} }); } // //也可不使用构造函数注入的方式,使用注解注入public变量的方式 // @Parameterized.Parameter // public int num; // //value = 1指定括号里的第二个Boolean值 // @Parameterized.Parameter(value = 1) // public boolean truth; @Test public void printTest() { Assert.assertEquals(truth, print(num)); System.out.println(num); } private boolean print(int num) { return num % 2 == 0; } } 复制代码Robolectric通过一套能运行在JVM上的Android代码,解决了在Java单元测试中很难进行Android单元测试的痛点。
首先给指定的测试类上面进行配置
@RunWith(RobolectricTestRunner.class) //目前Robolectric最高支持sdk版本为23。 @Config(constants = BuildConfig.class, sdk = 23) 复制代码下面是一些常用用法
//当Robolectric.setupActivity()方法返回的时候, //默认会调用Activity的onCreate()、onStart()、onResume() mTestActivity = Robolectric.setupActivity(TestActivity.class); //获取TestActivity对应的影子类,从而能获取其相应的动作或行为 ShadowActivity shadowActivity = Shadows.shadowOf(mTestActivity); Intent intent = shadowActivity.getNextStartedActivity(); //使用ShadowToast类获取展示toast时相应的动作或行为 Toast latestToast = ShadowToast.getLatestToast(); Assert.assertNull(latestToast); //直接通过ShadowToast简单工厂类获取Toast中的文本 Assert.assertEquals("hahaha", ShadowToast.getTextOfLatestToast()); //使用ShadowAlertDialog类获取展示AlertDialog时相应的 //动作或行为(暂时只支持app包下的,不支持v7。。。) latestAlertDialog = ShadowAlertDialog.getLatestAlertDialog(); AlertDialog latestAlertDialog = ShadowAlertDialog.getLatestAlertDialog(); Assert.assertNull(latestAlertDialog); //使用RuntimeEnvironment.application可以获取到 //Application,方便我们使用。比如访问资源文件。 Application application = RuntimeEnvironment.application; String appName = application.getString(R.string.app_name); Assert.assertEquals("WanAndroid", appName); //也可以直接通过ShadowApplication获取application ShadowApplication application = ShadowApplication.getInstance(); Assert.assertNotNull(application.hasReceiverForIntent(intent)); 复制代码自定义Shadow类
@Implements(Person.class) public class ShadowPerson { @Implementation public String getName() { return "AndroidUT"; } } @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 23, shadows = {ShadowPerson.class}) Person person = new Person(); //实际上调用的是ShadowPerson的方法,输出JsonChao Log.d("test", person.getName()); ShadowPerson shadowPerson = Shadow.extract(person); //测试通过 Assert.assertEquals("JsonChao", shadowPerson.getName()); } 复制代码注意: 异步测试出现一些问题(比如改变一些编码习惯,比如回调函数不能写成匿名内部类对象,需要定义一个全局变量,并破坏其封装性,即提供一个get方法,供UT调用),解决方案使用Mockito来结合进行测试,将异步转为同步。
Jacoco的全称为Java Code Coverage(Java代码覆盖率),可以生成java的单元测试代码覆盖率报告。
在应用Module下加入jacoco.gradle自定义脚本,app.gradle apply from它,同步,即可看到在app的Task下生成了Report目录,Report目录 下生成了JacocoTestReport任务。
apply plugin: 'jacoco' jacoco { toolVersion = "0.7.7.201606060606" //指定jacoco的版本 reportsDir = file("$buildDir/JacocoReport") //指定jacoco生成报告的文件夹 } //依赖于testDebugUnitTest任务 task jacocoTestReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') { group = "reporting" //指定task的分组 reports { xml.enabled = true //开启xml报告 html.enabled = true //开启html报告 } def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", includes: ["**/*Presenter.*"], excludes: ["*.*"])//指定类文件夹、包含类的规则及排除类的规则, //这里我们生成所有Presenter类的测试报告 def mainSrc = "${project.projectDir}/src/main/java" //指定源码目录 sourceDirectories = files([mainSrc]) classDirectories = files([debugTree]) executionData = files("${buildDir}/jacoco/testDebugUnitTest.exec")//指定报告数据的路径 } 复制代码在Gradle构建板块Gradle.projects下的app/Task/verification下,其中testDebugUnitTest构建任务会生成单元测试结果报告,包含xml及html格式,分别对应test-results和reports文件夹;jacocoTestReport任务会生成单元测试覆盖率报告,结果存放在jacoco和JacocoReport文件夹。
生成的JacocoReport文件夹下的index.html即对应的单元测试覆盖率报告,用浏览器打开后,可以看到覆盖情况被不同的颜色标识出来,其中绿色表示代码被单元测试覆盖到,黄色表示部分覆盖,红色则表示完全没有覆盖到。
要验证程序正确性,必然要给出所有可能的条件(极限编程),并验证其行为或结果,才算是100%覆盖条件。实际项目中,验证一般条件和边界条件就OK了。
在实际项目中,单元测试对象与页面是一对一的,并不建议跨页面,这样的单元测试耦合太大,维护困难。 需要写完后,看覆盖率,找出单元测试中没有覆盖到的函数分支条件等,然后继续补充单元测试case列表,并在单元测试工程代码中补上case。 直到规划的页面中所有逻辑的重要分支、边界条件都被覆盖,该项目的单元测试结束。
可以从公司项目小规模使用,形成自己的单元测试风格后,就可以跟大范围地推广了。
我的公众号 JsonChao 开通啦,如果您想第一时间获取最新文章和最新动态,欢迎扫描关注~
1、必知必会 | Android 测试相关的方方面面都在这儿
2、在Android Studio中进行单元测试和UI测试
3、Android单元测试(一)
4、Android单元测试(二)