Espresso 只做了半套的 Code Coverage

作者: _WZ_ | 来源:发表于2017-06-12 18:14 被阅读250次

    有在使用 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

    相关文章

      网友评论

        本文标题:Espresso 只做了半套的 Code Coverage

        本文链接:https://www.haomeiwen.com/subject/tvzlqxtx.html