(4.5.6.1)Android 代码覆盖率工具使用进阶
2017-04-28 10:57
323 查看
google官网上为开发者们介绍了Espresso测试框架,在之前的文章中已经讲到,该文章主要讲利用Espresso框架时如何获得测试代码覆盖率。
写了个例子在Github上:
[plain] view
plain copy
git clone https://github.com/LxxCaroline/EspressoJacocoSample.git
在工程的目录如下:
project: EspressoJacocoSample
--module: app
--module: mylibrary
在该工程中有两个模块,app和mylibrary,app依赖mylibrary,我在app下面写了测试代码,想要测测试代码对于mylibrary的覆盖率。
先看下app中build.gradle的配置
[plain] view
plain copy
apply plugin: 'com.android.application'
apply plugin:'jacoco'
jacoco{
toolVersion "0.7.4.201502262128"
}
android {
compileSdkVersion 15
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.example.hzlinxuanxuan.espressojacocosample"
minSdkVersion 9
targetSdkVersion 15
versionCode 1
versionName "1.0"
testInstrumentationRunner "com.example.hzlinxuanxuan.espressojacocosample.JUnitJacocoTestRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug{
testCoverageEnabled true
}
}
packagingOptions {
exclude 'LICENSE.txt'
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
debugCompile project(path: ':mylibrary', configuration: 'debug')
releaseCompile project(path: ':mylibrary', configuration: 'release')
// Testing-only dependencies
androidTestCompile 'com.android.support.test:runner:0.3'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
compile files('libs/junit-4.12.jar')
}
task jacocoTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){
group="Reporting"
description = "Generate Jacoco coverage reports"
//exclude auto-generated classes and tests
def fileFilter=['**/R.class',
'**/R$*.class',
'**/Manifest*.*',
'**/BuildConfig.*',
'android/**/*.*',
]
def debugTree=fileTree(dir:
"${rootDir}/mylibrary/build/intermediates/classes/debug",
excludes: fileFilter)
def sdkSrc="${rootDir}/mylibrary/src/main/java"
//指明对哪个目录下的代码进行绘制覆盖率统计图标
sourceDirectories=files([sdkSrc])
//指明对哪个目录下的代码进行覆盖率统计
classDirectories=files([debugTree])
additionalSourceDirs=files([
"${buildDir}/generated/source/buildConfig/debug",
"${buildDir}/generated/source/r/debug"
])
executionData=fileTree(dir:project.projectDir,includes:['**/*.exec','**/*.ec'])
reports{
xml.enabled=true
xml.destination="${buildDir}/jacocoTestReport.xml"
csv.enabled=false
html.enabled=true
html.destination="${buildDir}/reports/jacoco"
}
}
其中以下脚本是配置espresso
[plain] view
plain copy
androidTestCompile 'com.android.support.test:runner:0.3'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
其中以下脚本配置jacoco(生成测试覆盖率的工具)
[plain] view
plain copy
apply plugin:'jacoco'
jacoco{
toolVersion "0.7.4.201502262128"
}
task jacocoTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){
group="Reporting"
description = "Generate Jacoco coverage reports"
//exclude auto-generated classes and tests
def fileFilter=['**/R.class',
'**/R$*.class',
'**/Manifest*.*',
'**/BuildConfig.*',
'android/**/*.*',
]
def debugTree=fileTree(dir:
"${rootDir}/mylibrary/build/intermediates/classes/debug",
excludes: fileFilter)
def sdkSrc="${rootDir}/mylibrary/src/main/java"
//指明对哪个目录下的代码进行绘制覆盖率统计图标
sourceDirectories=files([sdkSrc])
//指明对哪个目录下的代码进行覆盖率统计
classDirectories=files([debugTree])
additionalSourceDirs=files([
"${buildDir}/generated/source/buildConfig/debug",
"${buildDir}/generated/source/r/debug"
])
executionData=fileTree(dir:project.projectDir,includes:['**/*.exec','**/*.ec'])
reports{
xml.enabled=true
xml.destination="${buildDir}/jacocoTestReport.xml"
csv.enabled=false
html.enabled=true
html.destination="${buildDir}/reports/jacoco"
}
}
task的类型为JacocoReport,JacocoReport是Gradle内置的一个类型,该类型的task用于生成Jacoco覆盖率报告
dependsOn: "connectedAndroidTest":这段是表明该task需要在connectedAndroidTest task完成之后进行,connectedAndroidTest是系统内置的自动运行测试工程的task,会默认在连接到电脑的设备上(有且仅有1台,否则会报错)执行全部的测试方法,如果dependsOn的task执行失败了则不会执行我们定义的task。由于我们使用的是adb命令运行测试脚本,因此不要添加这部分,直接写成task
jacocoTestReport(type: JacocoReport){}即可
reports代码块定义各种报告类型的开关,我们在这里开启了XML和HTML格式的报告输出
classDirectories代码块定义了生成报告使用的目标文件类,他的参数是一个FileCollection类型,我们可以使用FileTree来定义它,dir为目录名,includes后面为需要在报告中显示的文件,excludes为不需要在报告中显示的文件,如果不带includes及其参数会使用dir下的全部文件,否则需要按照其后的参数进行匹配仅使用符合匹配的文件,如果带excludes参数则会从已被选中的文件中在排除掉匹配其后参数的文件。<color
red>目标目录需要使用编译后的class文件,即./build/intermediates/classes/debug下的文件,而不是JAVA源码文件</color>
executionData代码块定义了要被统计的覆盖率文件的路径,该路径下的全部文件都会被用于覆盖率的计算
设置完成后运行该task即可生成Jacoco代码覆盖率报告,报告生成的路径为:./build/reports/jacoco/<task名>,其下有XML和HTML两份报告,HTML的报告长这样的:
当然还有一句重要的话,这句话是指明我们要使用的自定义的runner
[plain] view
plain copy
testInstrumentationRunner "com.example.hzlinxuanxuan.espressojacocosample.JUnitJacocoTestRunner"
在上面脚本中编写了一个task(如果不理解task可以看该篇文章Android--Gradle的理解),执行该task就可以执行测试用例,并得到结果。
接下来是编写测试代码和自定义runner,在该目录下创建
EspressoTest.Java中为测试代码
[java] view
plain copy
@RunWith(AndroidJUnit4.class)
@LargeTest
public class EspressoTest {
@Rule
public ActivityTestRule<MainActivity> actvRule = new ActivityTestRule(MainActivity.class);
@Test
public void testCase1() {
onView(withId(R.id.btn)).perform(click());
}
}
JUnitJacocoTestRunner.java:
[java] view
plain copy
public class JUnitJacocoTestRunner extends AndroidJUnitRunner {
static {
final String path = "/data/data/" + BuildConfig.APPLICATION_ID + "/coverage.ec";
System.setProperty("jacoco-agent.destfile", path);
}
@Override
public void finish(int resultCode, Bundle results) {
try {
Class rt = Class.forName("org.jacoco.agent.rt.RT");
Method getAgent = rt.getMethod("getAgent");
Method dump = getAgent.getReturnType().getMethod("dump", boolean.class);
Object agent = getAgent.invoke(null);
dump.invoke(agent, false);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
super.finish(resultCode,results);
}
}
在app/src/...../MainActivity.java中的代码
[java] view
plain copy
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View view){
LogUtil.d("there is a message");
}
}
在该该段代码中使用mylibrary模块中LogUtil的函数。
接下来看下mylibrary的build.gradle:
[plain] view
plain copy
apply plugin: 'com.android.library'
android {
publishNonDefault true
compileSdkVersion 23
buildToolsVersion "22.0.1"
defaultConfig {
minSdkVersion 9
targetSdkVersion 15
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
//需要在这里也添加,否则计算出来的覆盖率为0
debug{
testCoverageEnabled true
}
}
}
dependencies {
compile 'com.android.support:support-v4:19.+'
}
[plain] view
plain copy
testCoverageEnabled true
该句代码说明允许对该代码进行统计覆盖率,非常重要。
在最开始的时候我们讲到了一个task,执行该task可以执行测试用例,可以从该处找到task
如果一开始点开gradle,没有显示任何task的话,请点击左上方的刷新按钮,然后选择jacocoTestReport,双击就会执行测试用例。
要注意的是,如果有一个测试用例没有通过,则不能生成覆盖率的报告。
因为初始需求是测试mylibrary中的代码,所以在编写task的时候指定了使用哪个目录下
[plain] view
plain copy
def debugTree=fileTree(dir:
"${rootDir}/mylibrary/build/intermediates/classes/debug",
excludes: fileFilter)
def sdkSrc="${rootDir}/mylibrary/src/main/java"
//指明对哪个目录下的代码进行绘制覆盖率统计图标
sourceDirectories=files([sdkSrc])
//指明对哪个目录下的代码进行覆盖率统计
classDirectories=files([debugTree])
如果读者想要测试app下的代码,只需要改成如下即可
[plain] view
plain copy
def debugTree=fileTree(dir:
"${rootDir}/app/build/intermediates/classes/debug",
excludes: fileFilter)
def sdkSrc="${rootDir}/app/src/main/java"
//指明对哪个目录下的代码进行绘制覆盖率统计图标
sourceDirectories=files([sdkSrc])
//指明对哪个目录下的代码进行覆盖率统计
classDirectories=files([debugTree])
看到了么,只用修改目录路径即可。
生成测试报告之后去该目录下找
app\build\reports\jacoco
结果如下
打开index.html结果如下
当然你可以看到哪些代码被覆盖了
写了个例子在Github上:
[plain] view
plain copy
git clone https://github.com/LxxCaroline/EspressoJacocoSample.git
在工程的目录如下:
project: EspressoJacocoSample
--module: app
--module: mylibrary
在该工程中有两个模块,app和mylibrary,app依赖mylibrary,我在app下面写了测试代码,想要测测试代码对于mylibrary的覆盖率。
先看下app中build.gradle的配置
[plain] view
plain copy
apply plugin: 'com.android.application'
apply plugin:'jacoco'
jacoco{
toolVersion "0.7.4.201502262128"
}
android {
compileSdkVersion 15
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.example.hzlinxuanxuan.espressojacocosample"
minSdkVersion 9
targetSdkVersion 15
versionCode 1
versionName "1.0"
testInstrumentationRunner "com.example.hzlinxuanxuan.espressojacocosample.JUnitJacocoTestRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug{
testCoverageEnabled true
}
}
packagingOptions {
exclude 'LICENSE.txt'
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
debugCompile project(path: ':mylibrary', configuration: 'debug')
releaseCompile project(path: ':mylibrary', configuration: 'release')
// Testing-only dependencies
androidTestCompile 'com.android.support.test:runner:0.3'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
compile files('libs/junit-4.12.jar')
}
task jacocoTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){
group="Reporting"
description = "Generate Jacoco coverage reports"
//exclude auto-generated classes and tests
def fileFilter=['**/R.class',
'**/R$*.class',
'**/Manifest*.*',
'**/BuildConfig.*',
'android/**/*.*',
]
def debugTree=fileTree(dir:
"${rootDir}/mylibrary/build/intermediates/classes/debug",
excludes: fileFilter)
def sdkSrc="${rootDir}/mylibrary/src/main/java"
//指明对哪个目录下的代码进行绘制覆盖率统计图标
sourceDirectories=files([sdkSrc])
//指明对哪个目录下的代码进行覆盖率统计
classDirectories=files([debugTree])
additionalSourceDirs=files([
"${buildDir}/generated/source/buildConfig/debug",
"${buildDir}/generated/source/r/debug"
])
executionData=fileTree(dir:project.projectDir,includes:['**/*.exec','**/*.ec'])
reports{
xml.enabled=true
xml.destination="${buildDir}/jacocoTestReport.xml"
csv.enabled=false
html.enabled=true
html.destination="${buildDir}/reports/jacoco"
}
}
其中以下脚本是配置espresso
[plain] view
plain copy
androidTestCompile 'com.android.support.test:runner:0.3'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
其中以下脚本配置jacoco(生成测试覆盖率的工具)
[plain] view
plain copy
apply plugin:'jacoco'
jacoco{
toolVersion "0.7.4.201502262128"
}
task jacocoTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){
group="Reporting"
description = "Generate Jacoco coverage reports"
//exclude auto-generated classes and tests
def fileFilter=['**/R.class',
'**/R$*.class',
'**/Manifest*.*',
'**/BuildConfig.*',
'android/**/*.*',
]
def debugTree=fileTree(dir:
"${rootDir}/mylibrary/build/intermediates/classes/debug",
excludes: fileFilter)
def sdkSrc="${rootDir}/mylibrary/src/main/java"
//指明对哪个目录下的代码进行绘制覆盖率统计图标
sourceDirectories=files([sdkSrc])
//指明对哪个目录下的代码进行覆盖率统计
classDirectories=files([debugTree])
additionalSourceDirs=files([
"${buildDir}/generated/source/buildConfig/debug",
"${buildDir}/generated/source/r/debug"
])
executionData=fileTree(dir:project.projectDir,includes:['**/*.exec','**/*.ec'])
reports{
xml.enabled=true
xml.destination="${buildDir}/jacocoTestReport.xml"
csv.enabled=false
html.enabled=true
html.destination="${buildDir}/reports/jacoco"
}
}
task的类型为JacocoReport,JacocoReport是Gradle内置的一个类型,该类型的task用于生成Jacoco覆盖率报告
dependsOn: "connectedAndroidTest":这段是表明该task需要在connectedAndroidTest task完成之后进行,connectedAndroidTest是系统内置的自动运行测试工程的task,会默认在连接到电脑的设备上(有且仅有1台,否则会报错)执行全部的测试方法,如果dependsOn的task执行失败了则不会执行我们定义的task。由于我们使用的是adb命令运行测试脚本,因此不要添加这部分,直接写成task
jacocoTestReport(type: JacocoReport){}即可
reports代码块定义各种报告类型的开关,我们在这里开启了XML和HTML格式的报告输出
classDirectories代码块定义了生成报告使用的目标文件类,他的参数是一个FileCollection类型,我们可以使用FileTree来定义它,dir为目录名,includes后面为需要在报告中显示的文件,excludes为不需要在报告中显示的文件,如果不带includes及其参数会使用dir下的全部文件,否则需要按照其后的参数进行匹配仅使用符合匹配的文件,如果带excludes参数则会从已被选中的文件中在排除掉匹配其后参数的文件。<color
red>目标目录需要使用编译后的class文件,即./build/intermediates/classes/debug下的文件,而不是JAVA源码文件</color>
executionData代码块定义了要被统计的覆盖率文件的路径,该路径下的全部文件都会被用于覆盖率的计算
设置完成后运行该task即可生成Jacoco代码覆盖率报告,报告生成的路径为:./build/reports/jacoco/<task名>,其下有XML和HTML两份报告,HTML的报告长这样的:
当然还有一句重要的话,这句话是指明我们要使用的自定义的runner
[plain] view
plain copy
testInstrumentationRunner "com.example.hzlinxuanxuan.espressojacocosample.JUnitJacocoTestRunner"
在上面脚本中编写了一个task(如果不理解task可以看该篇文章Android--Gradle的理解),执行该task就可以执行测试用例,并得到结果。
接下来是编写测试代码和自定义runner,在该目录下创建
EspressoTest.Java中为测试代码
[java] view
plain copy
@RunWith(AndroidJUnit4.class)
@LargeTest
public class EspressoTest {
@Rule
public ActivityTestRule<MainActivity> actvRule = new ActivityTestRule(MainActivity.class);
@Test
public void testCase1() {
onView(withId(R.id.btn)).perform(click());
}
}
JUnitJacocoTestRunner.java:
[java] view
plain copy
public class JUnitJacocoTestRunner extends AndroidJUnitRunner {
static {
final String path = "/data/data/" + BuildConfig.APPLICATION_ID + "/coverage.ec";
System.setProperty("jacoco-agent.destfile", path);
}
@Override
public void finish(int resultCode, Bundle results) {
try {
Class rt = Class.forName("org.jacoco.agent.rt.RT");
Method getAgent = rt.getMethod("getAgent");
Method dump = getAgent.getReturnType().getMethod("dump", boolean.class);
Object agent = getAgent.invoke(null);
dump.invoke(agent, false);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
super.finish(resultCode,results);
}
}
在app/src/...../MainActivity.java中的代码
[java] view
plain copy
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View view){
LogUtil.d("there is a message");
}
}
在该该段代码中使用mylibrary模块中LogUtil的函数。
接下来看下mylibrary的build.gradle:
[plain] view
plain copy
apply plugin: 'com.android.library'
android {
publishNonDefault true
compileSdkVersion 23
buildToolsVersion "22.0.1"
defaultConfig {
minSdkVersion 9
targetSdkVersion 15
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
//需要在这里也添加,否则计算出来的覆盖率为0
debug{
testCoverageEnabled true
}
}
}
dependencies {
compile 'com.android.support:support-v4:19.+'
}
[plain] view
plain copy
testCoverageEnabled true
该句代码说明允许对该代码进行统计覆盖率,非常重要。
在最开始的时候我们讲到了一个task,执行该task可以执行测试用例,可以从该处找到task
如果一开始点开gradle,没有显示任何task的话,请点击左上方的刷新按钮,然后选择jacocoTestReport,双击就会执行测试用例。
要注意的是,如果有一个测试用例没有通过,则不能生成覆盖率的报告。
因为初始需求是测试mylibrary中的代码,所以在编写task的时候指定了使用哪个目录下
[plain] view
plain copy
def debugTree=fileTree(dir:
"${rootDir}/mylibrary/build/intermediates/classes/debug",
excludes: fileFilter)
def sdkSrc="${rootDir}/mylibrary/src/main/java"
//指明对哪个目录下的代码进行绘制覆盖率统计图标
sourceDirectories=files([sdkSrc])
//指明对哪个目录下的代码进行覆盖率统计
classDirectories=files([debugTree])
如果读者想要测试app下的代码,只需要改成如下即可
[plain] view
plain copy
def debugTree=fileTree(dir:
"${rootDir}/app/build/intermediates/classes/debug",
excludes: fileFilter)
def sdkSrc="${rootDir}/app/src/main/java"
//指明对哪个目录下的代码进行绘制覆盖率统计图标
sourceDirectories=files([sdkSrc])
//指明对哪个目录下的代码进行覆盖率统计
classDirectories=files([debugTree])
看到了么,只用修改目录路径即可。
生成测试报告之后去该目录下找
app\build\reports\jacoco
结果如下
打开index.html结果如下
当然你可以看到哪些代码被覆盖了
相关文章推荐
- (4.5.6)Android 代码覆盖率工具使用
- Eclipse与Android源码中ProGuard工具的使用--代码混淆
- Eclipse与Android源码中ProGuard工具的使用(代码混淆)
- Android代码优化——使用Android lint工具
- 单元测试和代码覆盖率工具的使用
- 展示C代码覆盖率的gcovr工具简介及相关命令使用示例
- Android 混淆代码学习以及Android加密工具--APKProtect的使用
- Android代码优化——使用Android lint工具
- android代码审查工具---lint工具的使用
- 使用lint工具优化Android代码
- Android代码优化——使用Android lint工具
- Android代码优化——使用Android lint工具
- Android 混淆代码学习以及Android加密工具--APKProtect的使用
- Android代码优化——使用Android lint工具
- 使用lint工具优化Android代码
- Android代码优化——使用Android lint工具
- 在Android上的使用代码覆盖工具
- Eclipse与Android源码中ProGuard工具的使用(代码混淆)
- Android代码优化—使用Android lint工具
- 测试代码覆盖率工具学习(Android Emma)