本文的书写已经是很早之前的事情了,只是把从新排版发了出来,有部分内网资源以及找不到了,有兴趣的同学可以根据原理补充上部分缺失的脚本。
由于这段很重要,特此在最前面的章节说明。
在多项目工程中(dolphin和topnews都是多项目工程),按照本文的方法仅能统计主工程的代码覆盖率,无法统计库工程的代码覆盖率,如需统计库工程的覆盖率请参考在多项目工程中统计子工程的覆盖率
Gradle是一个开源的,以 Groovy 语言为基础,面向Java应用为主。基于DSL(领域特定语言)语法的自动化构建工具,提供了强大的,可传递的依赖管理系统,目前的Dolphin和其他衍生产品都是使用Gradle进行编译的,他的官网地址为:http://www.gradle.org
Gradle的版本在一直不停的更新中,截至发稿日期止,最新的版本是2.3。不同的Gralde版本在个别语法定义上会稍有不同(如代码混淆2.1之前的版本为runProguard true,在之后的版本为minifyEnabled true),在搭建编译环境中最好和项目推荐的版本保持一致。
Gradle环境的搭建很简单,在官网下载对应版本的ZIP文件后解压缩,然后将bin目录加到环境变量中或者将gradle软链接到~/bin下即可
* 将bin目录加到环境变量:用文本编辑器或者VIM等工具打开~/.bashrc文件,在最后加上如下的内容即可
export PATH=
* 将gradle软链到~/bin目录下:
ln -s
以下是一个简单的Gradle脚本:
apply plugin: 'android'
apply plugin: "jacoco"
apply from: "$project.rootDir/DolphinBuild/common.gradle"
gradle.beforeProject { project ->
if (project.file('build.gradle').exists()) {
project.buildscript {
repositories {
maven {
name = "Baina Maven Proxy"
url = "http://mirrors.baina.com:8080/archiva/repository/internal"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:1.0.+'
}
}
}
}
android {
// update sdk version to 21, because some variables of LOLLIPOP are used
compileSdkVersion 21
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
instrumentTest.setRoot('$project.rootDir/DolphinRecordTest')
androidTest {
manifest.srcFile "$project.rootDir/DolphinRecordTest/AndroidManifest.xml"
java.srcDir "$project.rootDir/DolphinRecordTest/src"
res.srcDirs "$project.rootDir/DolphinRecordTest/res"
assets.srcDirs "$project.rootDir/DolphinRecordTest/assets"
}
}
defaultConfig {
testApplicationId "mobi.mgeek.TunnyBrowser.test"
testInstrumentationRunner "android.test.InstrumentationTestRunner"
}
buildTypes {
debug {
minifyEnabled true
proguardFile 'proguard-debug.cfg'
testCoverageEnabled true
}
release {
minifyEnabled true
proguardFile 'proguard-release.cfg'
}
}
jacoco {
version "0.7.1.201405082137"
}
}
jacoco {
toolVersion "0.7.1.201405082137"
}
dependencies {
androidTestCompile files("$project.rootDir/DolphinRecordTest/libs/robotium.jar")
}
task jacocoTestReport(type: JacocoReport, dependsOn: "connectedAndroidTest") {
def coverageSourceDirs = [
"src",
]
group = "Reporting"
description = "Generate Jacoco coverage reports after running tests."
reports {
xml.enabled true
html.enabled true
}
classDirectories = fileTree(
dir: "./build/intermediates/classes/debug",
includes: ["com/dolphin/browser/popup/RatingPopup*",
"com/dolphin/browser/popup/PopupManager*",
"mobi/mgeek/TunnyBrowser/BrowserActivity*",
"mobi/mgeek/TunnyBrowser/DeferredTaskManager*"],
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/BuildConfig.*',
'**/Manifest*.*']
)
sourceDirectories = files(coverageSourceDirs)
additionalSourceDirs = files(coverageSourceDirs)
executionData = fileTree(
dir: "$buildDir/outputs/code-coverage/connected"
)
}
Gradle是基于Groovy的脚本,关于Groovy的具体内容请参考:http://www.groovy-lang.org
接下来我们会对每个代码块做简要的介绍
gradle assemblDebug
通常Android项目的测试工程就在项目目录下的test目录下,当然为了不干扰源代码的结构,我们也可以将其放到和项目目录并列的文件夹下,以Dolphin项目为例,我们将测试项目(AndroidRecordTest)放在和主工程DolphinBrowserEN平级的目录下,在上面的介绍中我们知道gradle assemblDebug可以编译APK的debug版本,gradle assemblDebugTest便可以生成工程的测试工程,那么如何让gradle脚本识别出我们的测试工程的源码路径呢,在android代码块中有如下的描述:
android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
instrumentTest.setRoot('$project.rootDir/DolphinRecordTest')
androidTest {
manifest.srcFile "$project.rootDir/DolphinRecordTest/AndroidManifest.xml"
java.srcDir "$project.rootDir/DolphinRecordTest/src"
res.srcDirs "$project.rootDir/DolphinRecordTest/res"
assets.srcDirs "$project.rootDir/DolphinRecordTest/assets"
}
}
defaultConfig {
testApplicationId "mobi.mgeek.TunnyBrowser.test"
testInstrumentationRunner "android.test.InstrumentationTestRunner"
}
}
Jacoco是一个开源的测试代码覆盖率的框架,所谓代码覆盖率及在执行手动或自动化用例时同时记录源代码中每一行/没一个分支是否都被执行到了,以此来从一个方面反映测试用例是否覆盖了足够多的逻辑分支,我们引入Jacoco到我们的Dolphin工程中以期帮助手工用例差漏补缺已经检查白盒用例的完备性
首先需要集成jacoco plugin:
apply plugin: "jacoco"
之后在android块中声明使用的Jacoco的版本即可,同时在对应的BuildType中开启覆盖率统计
android {
jacoco {
version "0.7.1.201405082137"
}
buildTypes {
debug {
testCoverageEnabled true
}
}
}
这样编译出的Debug版本便被成功插桩可以用于覆盖率的统计了
覆盖率测试工具的设计本身是为了检验自动化脚本的覆盖率的,做自动化执行时可以通过选项设置将经过Jacoco插桩的Build包的执行记录记录下来,生成文件,和源码对比后生成覆盖率报告,因此我们先来简单讲讲如何验证自动化脚本的代码覆盖率
- 确保被测源码已经集成了Jacoco并编译其Debug版本
- 编译测试用的自动化脚本(可以使用Eclipse编译,或者按照之前的介绍将其Gradle化后使用Gralde编译)
- 将前面两步生成的APK安装到手机上
- 在手机上执行如下的命令,即可开始运行自动化脚本,并在生成覆盖率文件/sdcard/coverage.ec:
adb shell am instrument -w -e coverage true -e coverageFile /sdcard/coverage.ec mobi.mgeek.TunnyBrowser.test/android.test.InstrumentationTestRunner
其中最后一段为测试目标package和使用的TestRunner,可以使用如下命令查询当前已安装的全部测试APK及其对应的TestRunner:
adb shell pm list instrumentation
当然你也可以指定执行测试类或者TestSuite同样也是-e参数,key为class,value为被测类或TestSuite,甚至可以仅仅测试某个类中的某个方法(如仅需测试特定类的全部方法,不带#及其后的方法名即可):
adb shell am instrument -w -e coverage true -e coverageFile /sdcard/coverage.ec -e class free.dante.coverage.TC#testDemo mobi.mgeek.TunnyBrowser.test/android.test.InstrumentationTestRunner
- 将生成的覆盖率文件(在上面的例子中为/sdcard/coverage.ec)从手机中提取出来,准备后面的报告生成
adb pull /sdcard/coverage.rc
如果我们需要测试的是手工用例的代码覆盖率,我们需要一个小小的HACK手段,由于无法直接实现手动执行的覆盖率文件的生成,我们需要一个自动化脚本的引导文件帮助我们启动Dolphin和生成覆盖率文件,自动化组已经基本解决了问题.
下面讲一讲如何不录制测试脚本,直接手工执行用例。
原理:
在通过之前的步骤获取到了覆盖率文件后,我们可以通过一个gradle build来将这些覆盖率文件生成最终的XML和HTML格式的覆盖率报告,如果有多个覆盖率文件,生成报告时会自动Merge所有的执行内容,生成一份报告。
由于需要源码做分析,我们必须在源码工程的gradle.build中添加生成Jacoco报告的task,task内容在上面已经有了,我们再贴一遍并简述一下各个参数及其用法
task jacocoTestReport(type: JacocoReport, dependsOn: "connectedAndroidTest") {
def coverageSourceDirs = [
"src",
]
group = "Reporting"
description = "Generate Jacoco coverage reports after running tests."
reports {
xml.enabled true
html.enabled true
}
classDirectories = fileTree(
dir: "./build/intermediates/classes/debug",
includes: ["com/dolphin/browser/popup/RatingPopup*",
"com/dolphin/browser/popup/PopupManager*",
"mobi/mgeek/TunnyBrowser/BrowserActivity*",
"mobi/mgeek/TunnyBrowser/DeferredTaskManager*"],
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/BuildConfig.*',
'**/Manifest*.*']
)
sourceDirectories = files(coverageSourceDirs)
additionalSourceDirs = files(coverageSourceDirs)
executionData = fileTree(
dir: "$buildDir/outputs/code-coverage/connected"
)
}