单元测试,就是对我们写的代码进行最小粒度的测试,可以测试函数的执行情况及运行时间,用于减少应用发布后可能出现的BUG及性能问题。在新版本的Android Studio中,我们新建一个工程,就会自动生成单元测试目录及自动添加测试框架的依赖。
关于单元测试,需要依赖Junit4框架和AndroidJunit4框架(新建工程默认已依赖)。如果要进行UI自动化测试就需要依赖Espresso框架,为了能够测试常规操作无法执行的条件分支,还需要依赖Mockito框架。关于这些框架,本文不作重点介绍,推荐大家阅读这几篇文章:
Android单元测试只看这一篇就够了
Android利用Espresso进行UI自动化测试的方法详解
Mock及Mockito的使用
Android代码覆盖率工具的使用
Jacoco官网
掌握了以上文章中的内容,就算已经入门单元测试的知识了。
本文重点要讲的是如何给library工程生成单元测试覆盖率报告。
先加上默认真机测试的配置:
moulde的build.gradle:
android {
...
defaultConfig {
...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
添加以上配置,就可以在真机上进行单元测试了。
在每个module的build.gradle文件中打开测试覆盖率报告:
buildTypes {
debug {
testCoverageEnabled = true
}
...
}
添加以上配置后,同步一下gradle,就会生成"createDebugCovergeReport"的task(在gradle的verification分组下面)。
以上配置完成后,在项目根目录下运行命令:
./gradlew createDebugCoverageReport
运行这个task就会自动跑完androidTest和test目录下的单元测试代码,并且会在各个module的build/outputs/code-coverage/connected目录下面生成单元测试覆盖率二进行文件(*.ec格式),这个时候还不能查看像上图中html格式的覆盖率报告。接着往下看。
如果你的工程有library工程,它会在每个library工程下生成对应的测试覆盖率报告,但是它们没有合并在一起。这样我们就需要在各个library工程中写各自的单元测试代码。这样显示很麻烦。如果所有的单元测试都写在application moudle下就方便多了。试想一下,如果要查看一个项目的单元测试覆盖率,就要一个一个查看每个module下的测试报告,而且还要自己去算整体的测试用例覆盖情况,这样多不方便?有没有办法把所有module的测试报告合在一起呢?肯定是有的。我们期望的是只在applicaton module中写单元测试,并且生成的报告可以过滤一些不想统计的module。本文就是解决这个问题。
首先在你的applicaton module的build.gradle文件中加上以下脚本:
或者单独写一个report.gradle,放在config目录下,复制粘贴以下内容(第一行不要),然后在application module的build.gradle中引入report.gradle
apply from:'../config/report.gradle'
apply plugin: 'com.android.application'
apply plugin: 'jacoco'
jacoco {
//编译如果出现could not read execution data请修改这个版本号
toolVersion = "0.8.2"
}
task jacocoTestReport(type: JacocoReport) {
group = "Reporting"
description = "Generate Jacoco coverage reports"
sourceDirectories = files() //源码
classDirectories = files() //class字节码
//定义需要过滤的代码
def fileFilter = ['**/R.class', '**/R$*.class','**/BuildConfig.*', '**/Manifest*.*','android/**/*.*']
project.rootProject.allprojects.each { project ->
if (project.name != "app") { //报告中去除app工程中的代码
sourceDirectories += files("${project.projectDir}" + "/src/main/java")
classDirectories += fileTree(dir: "${project.projectDir}" + "/build/intermediates/classes/debug", excludes: fileFilter)
}
}
//build时打印路径
sourceDirectories.files.each {
logger.lifecycle(it.path)
}
classDirectories.files.each {
logger.lifecycle(it.path)
}
//设置ec文件源
executionData = fileTree(dir: "$buildDir", includes: ["outputs/code-coverage/connected/*.ec"])
reports {//报告生成目录
xml.enabled = false
html.enabled = true
html.destination file("${buildDir}/reports/allReports")
}
}
另外,推荐项目根目录下的build.gradle中配置的gradle插件(classpath那一行)版本使用3.1.1,3.2.1版本在module/build/intermediates目录下已经没有classes文件夹了,所有以上jacoco的配置是无效的。另外也不要使用1.5.0这种老的gradle插件,因为在老版本下,library工程默认打出的是release包,而我们单元测试用的是debug版的包。如果你使用1.5.0版gradle插件,你会发现你的单元测试代码编译不通过:都找不到library中的类。
所以推荐在工程根目录下的build.gradle文件中,将gradle插件改成3.1.1,如果使用其它版本没有问题也可以,因为这个版本已经验证过没有问题。
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
}
添加配置后,在工程根目录运行命令
./gradlew createDebugCoverageReport
执行此task后就会在xxx/build/outputs/code-coverage/connected路径下生xxx.ec文件。
再执行
./gradlew jacocoTestReport
就会在xxx/build/reports/allReports目录下生成所有工程的单元测试覆盖率报告。
注意,library工程不要添加任何与单元测试有关的框架和代码。
如果发现单元覆盖率报告是0%,不用担心,请检查你的app是否有文件读写权限,如果没有请加上文件读写权限,因为单元测试是需要SD卡做中转的。简单起见,可以将target sdk设成23以下,并在Manifest文件中声明读写权限即可。
如果发现执行jacocoTestReport生成报告时报“Unable to read execution data file coverage.ec”错误:
$ gradle jacocoTestReport
报如下错
* What went wrong:
Execution failed for task ':app:jacocoTestReport'.
> Unable to read execution data file E:\app\build\outputs\code-coverage\connected\coverage.ec
可能是你的jacoco版本太高了,请将jacoco版本改成"0.7.4+"
build.gradle修改:
jacoco {
toolVersion = "0.7.4+"
}