Android 多模块应用的测试覆盖率报告
Android test coverage report for multi module app
我们有一个多模块应用程序。我们有 3 个图书馆项目和 1 个启动项目。
module1(图书馆)
module2 (Libraray) 依赖于 module1
module3 (Libraray) 依赖于 module1
启动(没有任何源代码,它只是所有库的启动器)取决于模块 1 和模块 2。
在模块 1 中,我们使用外观模式访问模块 2 和模块 3 类。由于我们需要在 Launch 项目中编写所有测试用例,因为我们可以访问启动项目中的所有 类,这样我们就可以访问所有 类 并且测试用例不会因失败而失败到 NoClassDefException。
当我们在 Launch 项目中编写测试用例时,我们能够 运行 测试用例,我们得到 100% 的执行报告,并创建一个 index.html 文件,其中包含所有测试用例的详细信息,但是当我尝试生成覆盖率报告时,它没有显示覆盖率报告的任何数据。下面是我的 gradle 文件。
apply plugin: 'com.android.application'
apply plugin: 'jacoco'
android {
compileSdkVersion 22
buildToolsVersion "23.0.2"`
defaultConfig {
applicationId "com.test.mobile"
minSdkVersion 14
targetSdkVersion 17
multiDexEnabled true
testApplicationId "com.test.mobile.test"
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
}
repositories {
mavenCentral()
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
}
debug{
testCoverageEnabled true
}
}
dexOptions {
preDexLibraries = false
javaMaxHeapSize "4096M"
jumboMode = true
incremental false
}
afterEvaluate {
tasks.matching {
it.name.startsWith('dex')
}.each { dx ->
if (dx.additionalParameters == null) {
dx.additionalParameters = []
}
dx.additionalParameters += '--multi-dex'
dx.additionalParameters += "--main-dex-list=$projectDir\multidex-main-dex-list.txt".toString()
}
}}
dependencies {
compile project(':module2')
compile project(':module3')
compile "com.android.support.test.espresso:espresso-idling-resource:2.2.1"
// Dependencies for local unit tests
testCompile "junit:junit:4.12" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.mockito:mockito-all:1.10.19" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.hamcrest:hamcrest-all:1.3" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.powermock:powermock-module-junit4:1.6.2" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.powermock:powermock-api-mockito:1.6.2" exclude group: 'com.android.support', module: 'support-annotations'
// Android Testing Support Library's runner and rules
androidTestCompile "com.android.support.test:runner:0.4.1" exclude group: 'com.android.support', module: 'support-annotations'
androidTestCompile "com.android.support.test:rules:0.4.1" exclude group: 'com.android.support', module: 'support-annotations'
// Espresso UI Testing dependencies.
androidTestCompile "com.android.support.test.espresso:espresso-core:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api'
androidTestCompile "com.android.support.test.espresso:espresso-contrib:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api' exclude group: 'com.android.support', module: 'support-v4'
androidTestCompile "com.android.support.test.espresso:espresso-intents:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api'}
task jacocoTestReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') {
def projects = new ArrayList()
subprojects.each { prj ->
projects.add(prj)
}
reports {
xml.enabled = true
html.enabled = true
}
jacocoClasspath = configurations['androidJacocoAnt']
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
/*sourceDirectories = generateSourceFiles(projects)
classDirectories = generateClassDirs(projects)*/
executionData = files(["${buildDir}/jacoco/testDebugUnitTest.exec",
"${buildDir}/outputs/code-coverage/connected/coverage.ec"
])}
这是我们在顶部 build.gradle
中用于生成 HTML 覆盖率报告的内容:
def coverageSourceDirs = ['app/src/main/java', 'core/src/main/java', 'database/src/main/java']
def coverageExcludes = ['**/R.class',
'**/R$*.class',
'**/*$$ViewBinder*.*',
'**/inject/*',
'**/*$InjectAdapter.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/Dagger*.*',
'**/*_Provide*Factory.*',
'**/*_Member*Injector.*',
'**/*_Factory.*']
def coverageClassDirectories = [fileTree(dir: 'app/build/intermediates/classes/debug', excludes: coverageExcludes),
fileTree(dir: 'core/build/intermediates/classes/debug', excludes: coverageExcludes),
fileTree(dir: 'database/build/intermediates/classes/debug', excludes: coverageExcludes)]
task jacocoRootReport(type: JacocoReport) {
dependsOn "app:jacocoTestReport",
"core:jacocoTestReport",
"database:jacocoTestReport"
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
classDirectories = files(coverageClassDirectories)
executionData = files(tasks.getByPath("app:jacocoTestReport").executionData,
tasks.getByPath("core:jacocoTestReport").executionData,
tasks.getByPath("database:jacocoTestReport").executionData
)
reports {
html.enabled = true
xml.enabled = false
csv.enabled = false
}
onlyIf = {
true
}
doFirst {
executionData = files(executionData.findAll {
it.exists()
})
}
}
并且在每个子模块中:
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
testCoverageEnabled = true
}
}
}
def coverageSourceDirs = ['src/main/java']
task jacocoTestReport(type: JacocoReport, dependsOn: "testJenkinsUnitTest") {
group = "Reporting"
description = "Generate Jacoco coverage reports"
classDirectories = fileTree(dir: 'build/intermediates/classes/debug',
excludes: ['**/R.class',
'**/R$*.class',
'**/*$$ViewBinder*.*',
'**/inject/*',
'**/*$InjectAdapter.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/Dagger*.*',
'**/*_Provide*Factory.*',
'**/*_Member*Injector.*',
'**/*_Factory.*',
'**/PagerTitleStripV22*.*'])
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files('build/jacoco/testDebugUnitTest.exec')
doFirst {
new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
reports {
xml.enabled = false
html.enabled = true
}
}
请注意,如果您使用 Jenkins 插件或 Sonar 进行覆盖率分析,则不需要它。
P.S。如果您有任何问题,请手动检查所有路径,我可能会输入错误的内容。设置它真的很痛苦
我有 3 个名为 gcm_demo 的模块,googleservices 和 networkcommunication
所以在每个模块的 build.gradle 下
写
apply plugin: 'jacoco'
task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) {
reports {
xml.enabled = true
html.enabled = true
}
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
executionData = fileTree(dir: "$buildDir", includes: [
"jacoco/testDebugUnitTest.exec", "outputs/code-coverage/connected/*coverage.ec"
])
}
现在在项目 build.gradle 中编写以下脚本
apply plugin: 'jacoco'
task jacocoRootReport(type: JacocoReport, dependsOn: ['gcm_demo:jacocoTestReport', 'googleservice:jacocoTestReport', 'networkcommunication:jacocoTestReport']) {
reports {
xml.enabled = true
html.enabled = true
}
sourceDirectories = files([tasks.getByPath("gcm_demo:jacocoTestReport").sourceDirectories,
tasks.getByPath("googleservice:jacocoTestReport").sourceDirectories,
tasks.getByPath("networkcommunication:jacocoTestReport").sourceDirectories])
classDirectories = files([tasks.getByPath("gcm_demo:jacocoTestReport").classDirectories,
tasks.getByPath("googleservice:jacocoTestReport").classDirectories,
tasks.getByPath("networkcommunication:jacocoTestReport").classDirectories])
executionData = files([tasks.getByPath("gcm_demo:jacocoTestReport").executionData,
tasks.getByPath("googleservice:jacocoTestReport").executionData,
tasks.getByPath("networkcommunication:jacocoTestReport").executionData])
}
执行用
gradlew clean jRR (short abbreviation)
构建成功后输出文件夹为
{project location}\build\reports\jacoco\jacocoRootReport\html\index.html
它提供 UI 和 unitTest
的完整项目覆盖
除了使用 getByPath
之外,您还可以使用 build.gradle 本身的变量来访问不同的模块,例如 $buildDir
将带您到当前模块 build
文件夹。
其次,$project.projectDir.parent
将转到父项目。示例 $project.projectDir.parent/<sub-project-name>/outputs/code-coverage/connected/coverage.ec
您可以使用您的子项目名称:gcm_demo, googleservices or networkcommunication
作为 $project.projectDir.parent/gcm_demo/outputs/code-coverage/connected/coverage.ec
注意:确保您使用正确的文件 coverage.ec
或 coverage.exec
检查为您生成的内容
要打印所有路径,您可以在build.gradle中使用以下任务:
运行 gradle paths
定义为
task paths {
println "Printing the current module build: $buildDir"
println "Printing the module directory: $project.projectDir"
println "Printing the parent module: $project.projectDir.parent"
}
这将帮助您在多模块 android 项目中使用目录和文件夹。我的问题是无法从子模块 build.gradle
访问正确的文件夹和文件目录
我们有一个多模块应用程序。我们有 3 个图书馆项目和 1 个启动项目。
module1(图书馆) module2 (Libraray) 依赖于 module1 module3 (Libraray) 依赖于 module1
启动(没有任何源代码,它只是所有库的启动器)取决于模块 1 和模块 2。
在模块 1 中,我们使用外观模式访问模块 2 和模块 3 类。由于我们需要在 Launch 项目中编写所有测试用例,因为我们可以访问启动项目中的所有 类,这样我们就可以访问所有 类 并且测试用例不会因失败而失败到 NoClassDefException。
当我们在 Launch 项目中编写测试用例时,我们能够 运行 测试用例,我们得到 100% 的执行报告,并创建一个 index.html 文件,其中包含所有测试用例的详细信息,但是当我尝试生成覆盖率报告时,它没有显示覆盖率报告的任何数据。下面是我的 gradle 文件。
apply plugin: 'com.android.application'
apply plugin: 'jacoco'
android {
compileSdkVersion 22
buildToolsVersion "23.0.2"`
defaultConfig {
applicationId "com.test.mobile"
minSdkVersion 14
targetSdkVersion 17
multiDexEnabled true
testApplicationId "com.test.mobile.test"
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
}
repositories {
mavenCentral()
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
}
debug{
testCoverageEnabled true
}
}
dexOptions {
preDexLibraries = false
javaMaxHeapSize "4096M"
jumboMode = true
incremental false
}
afterEvaluate {
tasks.matching {
it.name.startsWith('dex')
}.each { dx ->
if (dx.additionalParameters == null) {
dx.additionalParameters = []
}
dx.additionalParameters += '--multi-dex'
dx.additionalParameters += "--main-dex-list=$projectDir\multidex-main-dex-list.txt".toString()
}
}}
dependencies {
compile project(':module2')
compile project(':module3')
compile "com.android.support.test.espresso:espresso-idling-resource:2.2.1"
// Dependencies for local unit tests
testCompile "junit:junit:4.12" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.mockito:mockito-all:1.10.19" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.hamcrest:hamcrest-all:1.3" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.powermock:powermock-module-junit4:1.6.2" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.powermock:powermock-api-mockito:1.6.2" exclude group: 'com.android.support', module: 'support-annotations'
// Android Testing Support Library's runner and rules
androidTestCompile "com.android.support.test:runner:0.4.1" exclude group: 'com.android.support', module: 'support-annotations'
androidTestCompile "com.android.support.test:rules:0.4.1" exclude group: 'com.android.support', module: 'support-annotations'
// Espresso UI Testing dependencies.
androidTestCompile "com.android.support.test.espresso:espresso-core:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api'
androidTestCompile "com.android.support.test.espresso:espresso-contrib:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api' exclude group: 'com.android.support', module: 'support-v4'
androidTestCompile "com.android.support.test.espresso:espresso-intents:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api'}
task jacocoTestReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') {
def projects = new ArrayList()
subprojects.each { prj ->
projects.add(prj)
}
reports {
xml.enabled = true
html.enabled = true
}
jacocoClasspath = configurations['androidJacocoAnt']
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
/*sourceDirectories = generateSourceFiles(projects)
classDirectories = generateClassDirs(projects)*/
executionData = files(["${buildDir}/jacoco/testDebugUnitTest.exec",
"${buildDir}/outputs/code-coverage/connected/coverage.ec"
])}
这是我们在顶部 build.gradle
中用于生成 HTML 覆盖率报告的内容:
def coverageSourceDirs = ['app/src/main/java', 'core/src/main/java', 'database/src/main/java']
def coverageExcludes = ['**/R.class',
'**/R$*.class',
'**/*$$ViewBinder*.*',
'**/inject/*',
'**/*$InjectAdapter.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/Dagger*.*',
'**/*_Provide*Factory.*',
'**/*_Member*Injector.*',
'**/*_Factory.*']
def coverageClassDirectories = [fileTree(dir: 'app/build/intermediates/classes/debug', excludes: coverageExcludes),
fileTree(dir: 'core/build/intermediates/classes/debug', excludes: coverageExcludes),
fileTree(dir: 'database/build/intermediates/classes/debug', excludes: coverageExcludes)]
task jacocoRootReport(type: JacocoReport) {
dependsOn "app:jacocoTestReport",
"core:jacocoTestReport",
"database:jacocoTestReport"
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
classDirectories = files(coverageClassDirectories)
executionData = files(tasks.getByPath("app:jacocoTestReport").executionData,
tasks.getByPath("core:jacocoTestReport").executionData,
tasks.getByPath("database:jacocoTestReport").executionData
)
reports {
html.enabled = true
xml.enabled = false
csv.enabled = false
}
onlyIf = {
true
}
doFirst {
executionData = files(executionData.findAll {
it.exists()
})
}
}
并且在每个子模块中:
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
testCoverageEnabled = true
}
}
}
def coverageSourceDirs = ['src/main/java']
task jacocoTestReport(type: JacocoReport, dependsOn: "testJenkinsUnitTest") {
group = "Reporting"
description = "Generate Jacoco coverage reports"
classDirectories = fileTree(dir: 'build/intermediates/classes/debug',
excludes: ['**/R.class',
'**/R$*.class',
'**/*$$ViewBinder*.*',
'**/inject/*',
'**/*$InjectAdapter.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/Dagger*.*',
'**/*_Provide*Factory.*',
'**/*_Member*Injector.*',
'**/*_Factory.*',
'**/PagerTitleStripV22*.*'])
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files('build/jacoco/testDebugUnitTest.exec')
doFirst {
new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
reports {
xml.enabled = false
html.enabled = true
}
}
请注意,如果您使用 Jenkins 插件或 Sonar 进行覆盖率分析,则不需要它。
P.S。如果您有任何问题,请手动检查所有路径,我可能会输入错误的内容。设置它真的很痛苦
我有 3 个名为 gcm_demo 的模块,googleservices 和 networkcommunication 所以在每个模块的 build.gradle 下 写
apply plugin: 'jacoco'
task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) {
reports {
xml.enabled = true
html.enabled = true
}
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
executionData = fileTree(dir: "$buildDir", includes: [
"jacoco/testDebugUnitTest.exec", "outputs/code-coverage/connected/*coverage.ec"
])
}
现在在项目 build.gradle 中编写以下脚本
apply plugin: 'jacoco'
task jacocoRootReport(type: JacocoReport, dependsOn: ['gcm_demo:jacocoTestReport', 'googleservice:jacocoTestReport', 'networkcommunication:jacocoTestReport']) {
reports {
xml.enabled = true
html.enabled = true
}
sourceDirectories = files([tasks.getByPath("gcm_demo:jacocoTestReport").sourceDirectories,
tasks.getByPath("googleservice:jacocoTestReport").sourceDirectories,
tasks.getByPath("networkcommunication:jacocoTestReport").sourceDirectories])
classDirectories = files([tasks.getByPath("gcm_demo:jacocoTestReport").classDirectories,
tasks.getByPath("googleservice:jacocoTestReport").classDirectories,
tasks.getByPath("networkcommunication:jacocoTestReport").classDirectories])
executionData = files([tasks.getByPath("gcm_demo:jacocoTestReport").executionData,
tasks.getByPath("googleservice:jacocoTestReport").executionData,
tasks.getByPath("networkcommunication:jacocoTestReport").executionData])
}
执行用
gradlew clean jRR (short abbreviation)
构建成功后输出文件夹为
{project location}\build\reports\jacoco\jacocoRootReport\html\index.html
它提供 UI 和 unitTest
的完整项目覆盖 除了使用 getByPath
之外,您还可以使用 build.gradle 本身的变量来访问不同的模块,例如 $buildDir
将带您到当前模块 build
文件夹。
其次,$project.projectDir.parent
将转到父项目。示例 $project.projectDir.parent/<sub-project-name>/outputs/code-coverage/connected/coverage.ec
您可以使用您的子项目名称:gcm_demo, googleservices or networkcommunication
作为 $project.projectDir.parent/gcm_demo/outputs/code-coverage/connected/coverage.ec
注意:确保您使用正确的文件 coverage.ec
或 coverage.exec
检查为您生成的内容
要打印所有路径,您可以在build.gradle中使用以下任务:
运行 gradle paths
定义为
task paths {
println "Printing the current module build: $buildDir"
println "Printing the module directory: $project.projectDir"
println "Printing the parent module: $project.projectDir.parent"
}
这将帮助您在多模块 android 项目中使用目录和文件夹。我的问题是无法从子模块 build.gradle