移动测试基础 Android UI 自动化测试的代码覆盖率

易寒 · 2015年05月06日 · 最后由 ice 回复于 2017年05月22日 · 226 次阅读
本帖已被设为精华帖!

代码覆盖率我们都知道,了解过 jacoco 也知道如何去弄,那么我们想要在跑 UI 自动化脚本的时候如何收集脚本执行过程中的代码覆盖率呢?首先我们知道 jacoco 可以得到测试的代码覆盖率,那么如果这些单测是 UI 的 case,是不是就可以达到我们的要求,我们尝试一下,参考文档

AS 中新建 android 项目

这里写图片描述
在 android studio 上新建一个 android 项目,默认 build.gradle 如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        applicationId "com.wuba.wuxian.android_0504"
        minSdkVersion 19
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'
}

执行测试

执行 connectedAndroidTest 任务,在 android studio 中,执行测试任务的任务名为 connectedAndroidTest(被坑了很久)。

58deMacBook-Pro:app wuxian$ gradle clean connectedAndroidTest
:app:clean                                                                     
:app:preBuild UP-TO-DATE     
:app:preDebugBuild UP-TO-DATE     
:app:compileDebugNdk UP-TO-DATE     
:app:checkDebugManifest                
:app:preReleaseBuild UP-TO-DATE      
:app:prepareComAndroidSupportAppcompatV72200Library                 
:app:prepareComAndroidSupportSupportV42200Library                 
:app:prepareDebugDependencies                 
:app:compileDebugAidl                 
:app:compileDebugRenderscript                 
:app:generateDebugBuildConfig                 
:app:generateDebugAssets UP-TO-DATE      
:app:mergeDebugAssets                 
:app:generateDebugResValues UP-TO-DATE      
:app:generateDebugResources                 
:app:mergeDebugResources                 
/Users/wuxian/Documents/sourcecode/self/Android_05042/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.0.0/res/drawable-hdpi-v4/abc_spinner_mtrl_am_alpha.9.png: libpng warning: iCCP: Not recognizing known sRGB profile that has been edited

:app:processDebugManifest                 
:app:processDebugResources                 
:app:generateDebugSources                 
:app:compileDebugJava                 
:app:preDexDebug                 
:app:dexDebug                 
:app:processDebugJavaRes UP-TO-DATE      
:app:validateDebugSigning                 
:app:packageDebug                 
:app:zipalignDebug                 
:app:assembleDebug                 
:app:preDebugAndroidTestBuild UP-TO-DATE      
:app:compileDebugAndroidTestNdk UP-TO-DATE      
:app:prepareDebugAndroidTestDependencies                 
:app:compileDebugAndroidTestAidl                 
:app:processDebugAndroidTestManifest                 
:app:compileDebugAndroidTestRenderscript                 
:app:generateDebugAndroidTestBuildConfig                 
:app:generateDebugAndroidTestAssets UP-TO-DATE      
:app:mergeDebugAndroidTestAssets                 
:app:generateDebugAndroidTestResValues UP-TO-DATE      
:app:generateDebugAndroidTestResources                 
:app:mergeDebugAndroidTestResources                 
:app:processDebugAndroidTestResources                 
:app:generateDebugAndroidTestSources                 
:app:compileDebugAndroidTestJava                 
:app:preDexDebugAndroidTest                 
:app:dexDebugAndroidTest                 
:app:processDebugAndroidTestJavaRes UP-TO-DATE      
:app:packageDebugAndroidTest                 
:app:assembleDebugAndroidTest                 
:app:connectedAndroidTest                 

BUILD SUCCESSFUL

Total time: 47.159 secs

执行成功后,我们去找测试报告,可千万别去 build 下找 reports 目录啊,真心不一样有没有,带 build/outputs/reports/androidTests/connected 目录下打开 index.html。

这里写图片描述

这里写图片描述

在 gradle 中加入 jacoco

在 build.gradle 中添加如下信息:

apply plugin: 'jacoco'
....
jacoco{
    toolVersion = "0.7.1.201405082137"
}
android {
    buildTypes {
            debug {
                testCoverageEnabled = true

            }

添加后的 build.gradle 如下所示:

apply plugin: 'com.android.application'
apply plugin: 'jacoco'

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        applicationId "com.wuba.wuxian.android_0504"
        minSdkVersion 19
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"

    }

    buildTypes {
        debug {
            testCoverageEnabled = true

        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'

}
jacoco {
    toolVersion = "0.7.1.201405082137"
}

执行 jacoco

执行获得代码覆盖率的报告命令为 createDebugCoverageReport

58deMacBook-Pro:app wuxian$ gradle clean createDebugCoverageReport 
:app:clean                                                                           
:app:preBuild UP-TO-DATE     
:app:preDebugBuild UP-TO-DATE     
:app:compileDebugNdk UP-TO-DATE     
:app:checkDebugManifest                
:app:preReleaseBuild UP-TO-DATE     
:app:prepareComAndroidSupportAppcompatV72200Library                 
:app:prepareComAndroidSupportSupportV42200Library                 
:app:prepareDebugDependencies                 
:app:compileDebugAidl                 
:app:compileDebugRenderscript                 
:app:generateDebugBuildConfig                 
:app:generateDebugAssets UP-TO-DATE      
:app:mergeDebugAssets                 
:app:generateDebugResValues UP-TO-DATE      
:app:generateDebugResources                 
:app:mergeDebugResources                 
/Users/wuxian/Documents/sourcecode/self/Android_05042/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.0.0/res/drawable-hdpi-v4/abc_spinner_mtrl_am_alpha.9.png: libpng warning: iCCP: Not recognizing known sRGB profile that has been edited

:app:processDebugManifest                 
:app:processDebugResources                 
:app:generateDebugSources                 
:app:compileDebugJava                 
:app:unzipJacocoAgent                 
:app:instrumentDebug                                                                  
:app:preDexDebug                 
:app:dexDebug                 
:app:processDebugJavaRes UP-TO-DATE      
:app:validateDebugSigning                 
:app:packageDebug                 
:app:zipalignDebug                 
:app:assembleDebug                 
:app:preDebugAndroidTestBuild UP-TO-DATE      
:app:compileDebugAndroidTestNdk UP-TO-DATE      
:app:prepareDebugAndroidTestDependencies                 
:app:compileDebugAndroidTestAidl                 
:app:processDebugAndroidTestManifest                 
:app:compileDebugAndroidTestRenderscript                 
:app:generateDebugAndroidTestBuildConfig                 
:app:generateDebugAndroidTestAssets UP-TO-DATE      
:app:mergeDebugAndroidTestAssets                 
:app:generateDebugAndroidTestResValues UP-TO-DATE      
:app:generateDebugAndroidTestResources                 
:app:mergeDebugAndroidTestResources                 
:app:processDebugAndroidTestResources                 
:app:generateDebugAndroidTestSources                 
:app:compileDebugAndroidTestJava                 
:app:preDexDebugAndroidTest                 
:app:dexDebugAndroidTest                 
:app:processDebugAndroidTestJavaRes UP-TO-DATE      
:app:packageDebugAndroidTest                 
:app:assembleDebugAndroidTest                 
:app:connectedAndroidTest                 
:app:createDebugCoverageReport                 

BUILD SUCCESSFUL

Total time: 1 mins 6.089 secs

ok,我们去查看代码覆盖率的报告:

报告目录:

这里写图片描述

报告形式:

这里写图片描述

加入 UI 脚本

上面的测试结果报告和代码覆盖率的报告都有了,但是我们的要求是执行 UI 脚本的时候,查看代码覆盖率的。所以我们现在加入 UI 脚本,我选择 Robotium 来编写自动化脚本,原因是 Robotium 是继承自 instrumentation 的框架,至于 uiautomator 的 case 看官方的介绍暂时还没有加入,以后可能会加入。

加入 robotium 依赖

androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.1'

添加依赖后的 build.gradle 如下:

apply plugin: 'com.android.application'
apply plugin: 'jacoco'

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        applicationId "com.wuba.wuxian.android_0504"
        minSdkVersion 19
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"

    }

    buildTypes {
        debug {
            testCoverageEnabled = true

        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'
    androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.1'
}
jacoco {
    toolVersion = "0.7.1.201405082137"
}

同步一下脚本后,我们会发现依赖包中多了一个 robotium 的 jar 包

这里写图片描述

在 app 模块下的 src/androidTest 目录下添加一个 MainActivityTest 的 case 类,这个 case 只是简单的打开关闭应用。

这里写图片描述

MainActivityTest 内容如下:

package com.wuba.wuxian.android_0504;

import android.test.ActivityInstrumentationTestCase2;

import com.robotium.solo.Solo;

/**
 * Created by wuxian on 15/5/6.
 */
public class MainActivityTest extends ActivityInstrumentationTestCase2 {
    private Solo solo;

    public MainActivityTest() {
        super(MainActivity.class);
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();
        solo = new Solo(getInstrumentation(), getActivity());
    }

    public void testStartClose() throws Exception {
        Thread.sleep(1000);
    }
    @Override
    public void tearDown() throws Exception {
        super.tearDown();

    }

}

然后我们执行gradle clean createDebugCoverageReport 命令来执行测试和生成代码覆盖率的报告

这里写图片描述

我们来看一下代码覆盖率的报告:

这里写图片描述

总结

跳过的坑

坑 1

2 天时间研究出来,超出我的预期,刚开始以为 android 项目和普通 java 项目就是简单的添加 jacoco,然后执行 test 和 jacoco 就能得到数据,后来实验的时候得不到数据,就添加了一个 jacocoTestReport 任务,还是不行,原来 android 已经将这些任务都内置到了 android plugin 里面去了。

任务 作用
connectedAndroidTest 执行 android 的 case
createDebugCoverageReport 产生代码覆盖率的报告
connectedCheck 包含上面 2 个任务

坑 2

报告的目录不是普通 java 项目的 build/reports 目录下,而是在 build/outputs/reports 目录下,包含了测试结果 androidTests 和代码覆盖率结果 coverage:
这里写图片描述

坑 3

要想执行代码覆盖率的任务,需要开启代码覆盖率的权限,就是在 build.gradle 设置testCoverageEnabled = true 如果这个不设置,你执行createDebugCoverageReport 任务时会报错:

58deMacBook-Pro:app wuxian$ gradle clean createDebugCoverageReport

FAILURE: Build failed with an exception.

* What went wrong:          
Task 'createDebugCoverageReport' not found in project ':app'.

* Try:                      
Run gradle tasks to get a list of available tasks. Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED                

Total time: 10.169 secs 

执行 connectedCheck 不会执行 createDebugCoverageReport 任务
这里写图片描述

后续

我们已经完成了在跑自动化 case 的时候统计代码覆盖率,如果我们想统计功能测试人员手动执行 case 的代码统计率呢?因为没有了自动化 case 来引导,所以就有点区别,好在Monkey给了点提示,我们明天来研究。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 45 条回复 时间 点赞

收藏了!

赞. 我最近也在研究 jacoco 在 java 的覆盖率.这个工具真是不错. 文章写的也很赞.
jacoco 支持在测试执行过程中 dump 出结果. 手工测试完, 加入一些 dump 操作就可以获取到.

易寒 #44 · 2015年05月06日 Author

#2 楼 @seveniruby 你又为我指引了一条路

#3 楼 @doctorq 上个星期我看了一遍官方的文档和部分的代码. 跟其他的朋友也讨论了好久, 鉴于你技术进步飞快,我加你到一个高阶的技术群.

#4 楼 @seveniruby 求加。。我也要弄这个玩意儿

#4 楼 @seveniruby 哦哦。你说的就是那个群。我懂了~

gradle 用于构建 Android 项目的坑不少

#8 楼 @xuxu 嗯是的呢

易寒 #10 · 2015年05月07日 Author

#4 楼 @seveniruby 我刚开始研究,还没深入。

GETTING START.

易寒 #12 · 2015年05月07日 Author

#11 楼 @anikikun 阿里困困你在说什么?

#12 楼 @doctorq 额。。你可以看加速世界和 Sword Art Online 来获取阿里困困这个语句的来源

易寒 #33 · 2015年05月07日 Author

#13 楼 @monkey 不懂你们二次元世界的人

赞一个

在 Android studio 上新建一个工程上执行 createDebugCoverageReport 是可以的,但在实际工程中执行 createDebugCoverageReport 确失败什么原因。

我产生的代码覆盖率结果都是百分之 0,请问知道是怎么回事吗?

#2 楼 @seveniruby 有个问题,我尝试 dump jacoco 的覆盖率结果后,生成报告时总是提示 Unable to read execution data file E:\coverage\app\build\outputs\code-coverage\connected\coverage.ec
,对此我表示很无奈,求指点

#2 楼 @seveniruby 我此前用过 emma 在 gradle 上生成了覆盖率,不过 emma 不再维护,想换用 jacoco,不知道可否帮我看一下,我是在一个 hello word 工程上试的,如果可以我把整个 project 发给你,帮我看一下吧,一共也没有十行代码,看是 dump 覆盖率的部分写的有问题,还是 gradle 的配置有问题,能给个邮箱之类的吗

20楼 已删除

在实践上面几种 jacoco 记录 UI 自动化和手工操作的覆盖率时,发现在 avd 虚拟机上的所有操作都是可以记录到生成的 index.html,但在真机或者同时使用真机和虚拟机时,生成的 index.html 就无法记录代码覆盖率(全部显示 0%)。想请教一下是否了解其原因。或者是否能提供一两个可行的方案。

@monkey @doctorq 同问楼上的问题,为什么使用真机的时候无法统计,可否有解决方案,求分享

#22 楼 @max 真机我以前试过的,都可以的。就是要确认的是 ec 和 em 都是不是正常生成了。方案就是 emma 或者 jacoco 都可以的

易寒 #24 · 2016年01月20日 Author

#22 楼 @max 我用的就是真机,真机是没问题的

#23 楼 @monkey 恩恩,我刚看了下,使用小米手机时,生成的 ec 文件 0 字节,换个三星的手机 ec 文件正常生成了,也统计到了代码覆盖率,呜呜,坑了我好久

#24 楼 @doctorq 恩,把小米手机换成三星的手机后就可以正常生成 ec 文件了,也可以正常统计代码覆盖率了,好奇怪

#25 楼 @max 那就对了。

庞大的东西太复杂了,兼容的东西更麻烦

我来挖坑了哈,想从 EMMA 换成 jacoco。请教两个问题,我在用 emma 的时候,想在 UI 上或者接口测试中统计覆盖率。都必须用 command line 的模式在测试环境中插桩。jacoco 也必须这样么? 还有一个问题是,如果代码更新了,更新前和更新后的 report 能合并么?

易寒 #30 · 2016年06月21日 Author

#29 楼 @ycwdaaaa 不需要,可以在界面上启动统计,Emma 也可以在界面上启动,不是必须在命令行下的。合并的目的是啥?

#30 楼 @doctorq 在界面启动?额,emma 有 UI 么?我之前都是在自动部署的脚本中插桩,然后在 Jenkins 中配置一个 job 来获取 report。我想知道,jacoco 相对于 emma 有什么很大的优势么? 如果差不多我就不大想换了

易寒 #15 · 2016年06月21日 Author

#31 楼 @ycwdaaaa Android gradle 官方自带 jacoco,对于 jacoco 更加友好,且一直在优化。

#32 楼 @doctorq 可惜俺不是做移动端的。。。。我现在是非互联网行业。。我们公司就没有手机软件。。。TO B 的业务

易寒 #34 · 2016年06月21日 Author

#33 楼 @ycwdaaaa 那就继续用 emma,但是还是希望拥抱变化,不要做等待被淘汰的人

#34 楼 @doctorq 我再去研究研究哈



你好,请问使用 jenkins 和 jacoco 结合,但是 job 运行完成后,展示出来的覆盖率为 0,而且点击进入 java 文件,无法显示出 java 源码,楼主遇到过么?


补充上条,index 中的覆盖率是这样的。

@doctorq 弄了一下午,还是 cover 为 0,只能来这边求助了。。。

#38 楼 @simple 确定下 exec 文件大小

#39 楼 @doctorq 产生不了 exec 文件,我看你加了一句 destfile,我加了就报错了

#40 楼 @simple 你加的路径是什么?以及报啥错误

#41 楼 @doctorq 我运行 gradle test 的结果是这样的:

:TesterDemo:assembleReleaseUnitTest UP-TO-DATE
:TesterDemo:testReleaseUnitTest UP-TO-DATE
:TesterDemo:test UP-TO-DATE

BUILD SUCCESSFUL

Total time: 27.896 secs

然后我执行了 gradle jacocoTestReport 命令,结果是这样的:

Observed package id 'add-ons;addon-google_apis-google-19' in inconsistent location 'D:\android-
:\android-sdk\add-ons\addon-google_apis-google-19')
Incremental java compilation is an incubating feature.
It is no longer possible to set the Jacoco version in the jacoco {} block.
To update the version of Jacoco without updating the android plugin,
add a buildscript dependency on a newer version, for example: buildscript{    dependencies {
        classpath"org.jacoco:org.jacoco.core:0.7.4.201502262128"    }}
:TesterDemo:jacocoTestReport UP-TO-DATE

BUILD SUCCESSFUL

我的配置文件是这样写的:

apply plugin: 'com.android.application'
apply plugin: "jacoco"

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.exaple.testerdemo"
        minSdkVersion 8
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            testCoverageEnabled true
        }
        debug{
            testCoverageEnabled true
        }
    }

    jacoco {
        version "0.7.7.201606060606"
    }
}

//jacoco {
//  toolVersion "0.7.7.201606060606"
//  reportsDir = file("$buildDir/reports")
//}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile files('libs/proguard-sdk.jar')
    compile 'com.android.support:appcompat-v7:23.3.0'
}

task jacocoTestReport(type:JacocoReport) {
    group = "Reporting"

    description = "Generate Jacoco coverage reports"

//     exclude auto-generated classes and tests
    def fileFilter = ['**/R.class', '**/R$*.class',
                      '**/BuildConfig.*', '**/Manifest*.*',
                      'android/**/*.*']
    def debugTree = fileTree(dir:
            "${buildDir}/intermediates/classes",
            excludes: fileFilter)
    def mainSrc = "${project.projectDir}/src/main/java"

    sourceDirectories = files([mainSrc])
    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"
    }
}
易寒 #43 · 2016年08月17日 Author

#42 楼 @simple 提示信息貌似 jacoco 添加方式不对,可以按提示搜索一下

博主 借问下是不是需要获取手机上的 ec 覆盖率文件在抓出来转为 html。。?生成的 ec 为 0 字节

#37 楼 @1875884881 我情况跟你相反 能请教么

问一下 混合型应用的覆盖率也能被检测到吗?比如安卓壳套一个 webview 这种

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册