Espresso 只做了半套的 Code Coverage

有在使用 Espresso 撰写测试程序的人应该都知道,在 Android Studio 中 Android Test 类型的 Configuration 是不能使用 Code Coverage 的,最少在 Android Studio 2.2.2 仍然是如此。也就是说“Run 'xxx' with Coverage”的按钮没有办法按,情况如下图所示:

这点和 JUnit 的 Configuration 不同:


产出 Coverage Report 的第一步

以上的限制,对认真撰写测试程序的人来说,会造成很大的不便。Code Coverage 是撰写测试程序的一项重要指标,没有了 Code Coverage 就如同在伸手不见五指的黑暗中行走,完全不知身在何处,有关更多 Code Coverage 用途的细节,请参考先前的这篇文章

不过,说是做了半套,代表并不是完全没有办法产生 Coverage Report。要在 Espresso 测试运行的过程中产生 Coverage Report,要先调整 build.gradle,在 buildTypes 设定中开启 testCoverageEnabled 选项。

android {
   buildTypes {
      debug {
         testCoverageEnabled = true
      }
   }
}

完成以上修改,并执行 ./gradlew createDebugCoverageReport 指令成功之后,就可以在 Module 的 /build/reports/coverage/debug/ 路径下,以 Browser 开启 index.html

以上的步骤在搜寻之后,都可以取到一堆的文件提供相关的操作细节。但本篇到这里还没有结束,这个方法仅适用于测试和待测对象在同一个 Module 中。如果是在不同的 Module 里,由 Coverage 的 Report 页面中,其实可以看出清单中的 Package 都只列出测试程序所在 Module 的 Class。

扩增 Coverage Report 的范围

假设有一个如下所示的项目结构:

+ Sample
  + app
    + src
      + androidTest
      + main
        + com.sample.app
      + test
  + domain
    + src
      + main
        + com.sample.domain

其中 app 是 Android Application,domain 是 Android Library。如果把所有的测试 Class 放在 app 的 Module 之下,在 test 路径下的测试可经由 Configuration 的 Code Coverage 画面,来把 domain 中的 Class 列入 Report 产生的范围。

androidTest 中的测试就没有这么直接了,以 Android 的 Gradle Plugin 官方文件内容来看,目前并没有相关可调整的参数。所以这个阶段的目标,就是让 androidTest 产出的 Report 和 test 一样,可以把指定位置的 Class 列入 Coverage 的范围内。

要达到这个目标,首先要引入 JaCoCo 的 Plugin,透过 JaCoCo 的参数调整来突破原本的限制。在 JaCoCo 中有 sourceDirectoriesclassDirectories 二项参数,用来指定 Report 要涵盖的范围。以上面专案结构的例子来说,需要产生以下的内容在 appbuild.gradle 中:

apply plugin: 'jacoco' 

task jacocoTestReport(type: JacocoReport, dependsOn: "createDebugCoverageReport") {
   group = "Reporting"
   description = "Generate Jacoco coverage reports."

   reports {
      xml.enabled = false
      html.enabled = true
   }

   def classFilter = ['**/R.class',
                      '**/R$*.class',
                      '**/BuildConfig.*',
                      '**/Manifest*.*',
                      '**/*Test*.*',
                      'android/**/*.*']
 
   classDirectories = files(["${project.buildDir}/intermediates/classes/debug",
                             "${project(:domain).buildDir}/intermediates/classes/debug")
   sourceDirectories = files(["${project.projectDir}/src/main/java", 
                              "${project(:domain).projectDir}/src/main/java"])
   executionData = fileTree(dir: "${project.buildDir}", 
                            includes: "**/*.ec")

   afterEvaluate {
      classDirectories = files( classDirectories.files.collect {
         fileTree(dir: it, exclude: classFilter)
      })
   }
}

在这里附带说明一下,其实可以把以上的内容独立成单独的文件,例如:jacoco.gradle,再透过 apply from: 'jacoco.gradle' 的方式引用。一来可以简化 build.gradle 的内容、方便维护,二来可以避免调整 JaCoCo 的选项时,IDE 频繁地出现要求同步的讯息。

另外有一点需要注意的是,createDebugCoverageReport 产出的 executionData 是以 *.ec 为名称,与一般 JaCoCo 使用的 *.exec 不同。

设定好以上的内容之后,就可以执行 ./gradlew app:jacocoTestReport。和 createDebugCoverageReport 一样会产出 html 格式的 Report,但不同的是 index.html 会在 app/build/reports/jacoco/jacocoTestReport/html 路径下看到。这个路径是默认的,可以透过参数调整。

修正 Coverage Report 的问题

到这里所有的问题都解决了吗?其实并没有!照着以上的内容所产出的 Report 中,domain 下的 Class 不论测试怎么做,在 Instruction 和 Branch 的 Coverage 都呈现 0%,所以显然有一个环节出现了问题。

花了一番功夫发现 domain 在执行 jacocoTestReport 时都只会产生 release 的 Class。

就算是把 classDirectories 的路径调整成 release,在产出的 Report 中 Coverage 依然是 0%。目前解决方案是要调整 domain 的引用方式:

dependencies {
   debugCompile project(path: ':domain', configuration: 'debug')
   releaseCompile project(path: ':domain', configuration: 'release')
}

而在 domaindefaultConfig 下也要增加以下的设定:

android {
   defaultConfig {
      publishNonDefault true
  }
}

使用以上内容再执行一次 ./gradlew app:jacocoTestReport,就可以由 Report 中看到,domain 下的 Class 不再全都是 0% 的状态。

仍需改善的部份

虽然 Report 已经可以顺利的产出,但是与 JUnit 可以在 IDE 中直接检视 Coverage 情况的经验相比,还是有差别。毕竟没有办法直接在 IDE 对照着 Code 就可以了解 Coverage 的情况,要在 IDE 与 Browser 间来回地切换其实非常地不方便。

原本有试着把 createDebugCoverageReport 产出的 executionData,经由以下【Analyze -> Show Coverage Data...】功能所显示的 Window 载入。但这个功能似乎只能载入 *.exec 的文件,就算是把 *.ec 改为 *.exec,载入之后 Coverage 的状态也都呈现 0% 的结果。可能是真的有格式上相容的问题,所以才会把产出的 executionData 以 *.ec 来区格,避免被 Android Studio 载入。

只能期待未来 Android Studio 在改版时,能将这一部份的功能纳进入,既然在 Plugin 都已经可以产出 Report 了,把 Plugin 的动作整合到 IDE 的 Coverage 按钮上,应该不是什么难事吧!

更多深入的文章请参阅 http://www.jianshu.com/u/fea63707e07f

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,117评论 25 707
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    passiontim阅读 15,429评论 2 45
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,656评论 18 139
  • 一次,去拜会一位事业上颇有成就的朋友,闲聊中谈起了命运。我问:这个世界到底有没有命运?他说:当然有啊。我再问:命运...
    企业经营管理秘籍阅读 184评论 0 0
  • 1 读者笑笑给我留言说,“我老公和我结婚以后经常丢三落四的,就家里的门钥匙都丢了不下4次了,他的钱包和银行卡更甚,...
    北苏阅读 267评论 6 5