Android项目集成Jenkins(JUnit test & Coverage)

为了实现持续集成,提高代码质量,项目要求集成Jenkins,第一次集成Jenkins,花了不少时间,终于还是完成了,这里记录一下整个过程,Jenkins支持很多功能,Android Lint、Check Style、PMD、FindBugs、JUnit Test Report、Coverage Report等等,做下来感觉Coverage这部分的集成相对困难,所以本文主要以”如何在Jenkins中集成Junit & Coverage Report“为主题(至于怎么写JUnit test,不是本文的主题,我们假设项目已经支持JUnit环境了)。

关于Jenkins的集成,网上的文章不算多,这里有一个系列博客比较有参考价值:
http://blog.csdn.net/it_talk/article/details/50267573
有兴趣的可以深入研究一下。我这里会简略提一些必要的集成步骤,还会补充一些集成过程中遇到问题的解决。

1.编写coverage.gradle:
我先把最终的完成脚本贴出来,然后再分别说明。
//代码覆盖率插件
apply plugin: 'jacoco'

android {
// 目前已经不支持通过jacoco{}指定版本了,如果加入这块代码,运行不会出错,但是会有
// warning提示
//    jacoco{
//        version "0.7.6.201602180812"
//    }

    // 项目较小的情况下不需要dexOptions的配置,只有项目方法数超过64K的限制时或者
    // 项目本身接近64K上限但还未超过,加了Junit代码后就超过时(我们项目就属于这种
    // 情况),需要对项目做multidex支持,实现方法很简单,请自行调查。
    // Enable multidex后可能会遇到java.lang.OutOfMemoryError: GC overhead limit exceeded问题,需要此项配置来解决
    dexOptions {
        javaMaxHeapSize "4g"
    }

    buildTypes {
        debug{
            testCoverageEnabled true
        }
    }
}

//jacocoTestReport依赖于connectedAndroidTest task,所以在执行jacoco之前需要先执行connectedAndroidTest,也就是说需要连接测试机(模拟器or真机)
task jacocoAndroidTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports{
        xml.enabled false
        html.enabled true
        csv.enabled false
    }
    classDirectories = fileTree(
            dir : "$buildDir/intermediates/classes/debug",
            excludes : [
                    '**/*Test.class',
                    '**/R.class',
                    '**/R$*.class',
                    '**/BuildConfig.*',
                    '**/Manifest*.*'
            ]
    )
    def coverageSourceDirs = ['src']
    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
    additionalClassDirs = files(coverageSourceDirs)
    executionData = files("$buildDir/outputs/androidTest-code-coverage/connected/coverage.ec")
}

//jacocoTestReport依赖于test task,所以在执行jacoco之前需要先执行test
task jacocoTestReport(type:JacocoReport,dependsOn:"test"){
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports{
        xml.enabled false
        html.enabled true
        csv.enabled false
    }
    classDirectories = fileTree(
            dir : "$buildDir/intermediates/classes/debug",
            excludes : [
                    '**/*Test.class',
                    '**/R.class',
                    '**/R$*.class',
                    '**/BuildConfig.*',
                    '**/Manifest*.*'
            ]
    )
    def coverageSourceDirs = ['src']
    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
    additionalClassDirs = files(coverageSourceDirs)
    executionData = files("$buildDir/outputs/test-code-coverage/coverage.ec")
}


其中有两个task:jacocoAndroidTestReport、jacocoTestReport。从他们各自的依赖关系
task jacocoAndroidTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest")

task jacocoTestReport(type:JacocoReport,dependsOn:"test")

我们可以知道jacocoAndroidTestReport其实是针对androidTest测试的,而jacocoTestReport是针对test测试的。androidTest和test是什么?简单的说androidTest就是跑在Android模拟器(AVD)或者真机上的测试代码(包括Android UI测试),test就是跑在jvm上的测试代码,其实就是纯java的iunit test(和server端的junit没啥分别),他们还有各自对应的compile——”androidTestCompile"和"testCompile",关于这部分知识,不清楚的同学可以自行google,总之根据项目需要选择task。

2.将coverage.gradle导入项目的build.gradle中:
apply plugin: 'com.android.library'
// 这里是相对路径,假设coverage.gradle和build.gradle在同一个目录下
apply from: './coverage.gradle'

这样本地的脚本就算完成了,接下来需要配置Jenkins。Jenkins集成coverage其实会依赖JAVA、Android、Gradle环境,如果Jenkins搭在本地,可能就不需要这些步骤(毕竟做开发的不可能没有这些环境),但如果是搭在一台新的远程服务器上,那你可能需要事先搭建以上3个环境,配好环境变量,我没有实际验证过这3个环境是不是必须的,只是我们的远程服务器上Jenkins运行gradle脚本时一开始提示“can't find project ‘gradle’”,于是我单独配置了这些环境,之后还需要保证Jenkins服务器的网络Fan Qiang,否则很可能gradle脚本运行过程中下载一些必要的依赖会失败。

首先在Jenkins上新建一个“构建”,指定要运行的gradle task:

起初我是选择的“Invoke Gradle”,这个需要在服务器上配置gradle环境,但我配好运行还是有问题,所以我换了“Use Gradle Wrapper”,Jenkins会使用我们的gradle wrapper去自动下载gradle,这里就需要保证网络畅通了。“Root Build Script”就是指定你的build.gradle路径,如果不确定${workspace}指向的路径到底是哪里,可以直接运行一下,根据Jenkins日志提示的错误判断出来,总之你要指定正确的build.gradle路径,使Jenkins能找到它,之所以添加一个clean的task,是因为junit运行一次后下次运行如果不删除之前的结果会报错,所以运行之前先clean一次。

然后添加一个”构建后操作“,选择“Publish Junit test result report”:

xml的路径设定为JUnit执行后的XML报告的路径。

之后再添加一个”构建后操作“,选择“Record JaCoCo coverage report”:

“Path to class directories”配置的是编译后.class文件的路径地址,Android都是放在build路径下build\intermediates\classes;“Path to source directories”配置的是java代码路径。

这样一来,Jenkins就会在构建过程中执行我们的task,生成junit结果报告和coverage报告,构建后就会自动收集结果病生成报告。

接下来我们分析一下coverage.gradle,以及上述集成过程中可能会遇到的问题

一般来说,通过以下4步即可完成覆盖率的集成:
(1)引入jacoco插件;
//代码覆盖率插件
apply plugin: 'jacoco'

(2)打开coverage:
android {
    buildTypes {
        debug{
            testCoverageEnabled true
        }
    }
}

(3)创建task:
即jacocoAndroidTestReport 和 jacocoTestReport。
(4)将coverage.gradle导入项目的build.gradle中:
apply plugin: 'com.android.library'
// 这里是相对路径,假设coverage.gradle和build.gradle在同一个目录下
apply from: './coverage.gradle'

如此,你的项目应该已经集成了coverage功能,但是如果你的项目很大,不得不分包(只有androidTest下面的测试可能会遇到该问题)时,就需要对项目做multidex的支持,之后你会发现64K的问题没有了,但是你可能又遇到新的问题:
Error:UNEXPECTED TOP-LEVEL ERROR:
java.lang.OutOfMemoryError: GC overhead limit exceeded

此时,下面的设置就派上用场了:
dexOptions {
        javaMaxHeapSize "4g"
    }

好了,GC的问题也解决了,终于能跑通了,但是控制台还是会出现一些warnings,虽然不影响构建过程,但强迫症的同学都不愿意看到它们,其实解决方法很简单。

warning_1:
To honour the JVM settings for this build a new JVM will be forked. Please consider using the daemon

解决方法:在gradle.propertie文件中增加以下配置
org.gradle.daemon=true


warning2:
Running dex as a separate process.
To run dex in process, the Gradle daemon needs a larger heap.
It currently has 1996 MB.
For faster builds, increase the maximum heap size for the Gradle daemon to at least 4608 MB (based on the dexOptions.javaMaxHeapSize = 4g).
To do this set org.gradle.jvmargs=-Xmx4608M in the project gradle.properties.
For more information see https://docs.gradle.org/current/userguide/build_environment.html

关于这个warning的原因,可以看这里 http://stackoverflow.com/questions/37090135/to-run-dex-in-process-the-gradle-daemon-needs-a-larger-heap-it-currently-has-a,其实就是你指定了 javaMaxHeapSize "4g" 后引起的(android studio默认的是1g)
解决方法:在gradle.propertie文件中增加以下配置
# Default value: -Xmx1024m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx4608m -XX:MaxPermSize=1024m

以上就是整个过程了,如果大家在集成中遇到其他问题,就请自行解决吧。

猜你喜欢

转载自lovelease.iteye.com/blog/2320229
今日推荐