最近搞Android单元测试和集成测试,涉及到测试覆盖率工具Jacoco,网上文章很多,但都不全,把一些关键点记录一下,避免以后走弯路。
androidTest与test:
- androidTest
需要Android SDK才能运行的测试用例,可以与UI相关,也可以与UI无关。需要在真机或虚拟机上运行。一般使用的框架有:Instrumentation、Espresso。我理解成集成测试。
- test
test包下的测试用例运行不需要Android SDK,只需要JDK。这种一般只存放单元测试用例,框架一般为JUnit。但Android几乎无法实现MV完全解耦,所以需要用Mock的方式运行测试用例,常用框架:Mockito。
androidTest运行Jacoco
在Module gradle文件中添加以下代码:
apply plugin : 'com.android.application'
apply plugin: 'jacoco'
buildTypes {
debug {
testCoverageEnabled true
}
}
设置了这两项之后,运行androidtest:
- 打开右侧的Gradle面板运行connectedAndroidTest
- Terminal控制台输入 gradlew connectedAndroidTest
会自动生成集成测试报告和测试覆盖率报告
单元测试运行Jacoco
对于test包下,如果使用jacoco生成覆盖率将会麻烦一点点
需要在gradle中自定义task,代码如下:
task jacocoTestReport(type: JacocoReport, dependsOn: ["testDebugUnitTest", "connectedAndroidTest"]) {
group = "jacoco"
description = "Generate Jacoco coverage reports"
reports {
xml.enabled = true
html.enabled = true
}
def classDir = fileTree(
dir: "${project.buildDir}/intermediates/classes/debug",
excludes: ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', 'android/**/*.*'])
def sourceDir = "${project.projectDir}/src/main/java"
sourceDirectories = files([sourceDir])
classDirectories = files([classDir])
additionalSourceDirs = files(["${buildDir}/generated/source/buildConfig/debug", "${buildDir}/generated/source/r/debug"])
executionData = fileTree(dir: project.projectDir, includes: ['**/*.exec', '**/*.ec'])
}
创建task的时候,设置dependsOn: [“testDebugUnitTest”, “connectedAndroidTest”],就会在运行jacocoTestReport之前先运行单元测试和集成测试。
添加task之后在右侧gradle面板中会发现,多了jacocoTestReport任务:
运行之后会在图示的文件夹下生成单测的覆盖率报告:
jacococ.gradle
可以把Jacoco相关的写成一个gradle文件,供其他module的build.gradle引用:
apply plugin: 'jacoco'
android {
buildTypes {
debug {
testCoverageEnabled true
}
}
}
task jacocoTestReport(type: JacocoReport, dependsOn: ["testDebugUnitTest", "connectedAndroidTest"]) {
group = "jacoco"
description = "Generate Jacoco coverage reports"
reports {
xml.enabled = true
html.enabled = true
}
def classDir = fileTree(
dir: "${project.buildDir}/intermediates/classes/debug",
excludes: ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', 'android/**/*.*'])
def sourceDir = "${project.projectDir}/src/main/java"
sourceDirectories = files([sourceDir])
classDirectories = files([classDir])
additionalSourceDirs = files(["${buildDir}/generated/source/buildConfig/debug", "${buildDir}/generated/source/r/debug"])
executionData = fileTree(dir: project.projectDir, includes: ['**/*.exec', '**/*.ec'])
}