Android project integration Jenkins (JUnit test & Coverage)

In order to achieve continuous integration and improve code quality, the project requires the integration of Jenkins. It took a lot of time to integrate Jenkins for the first time, but it was finally completed. Here is a record of the whole process. Jenkins supports many functions, such as Android Lint, Check Style, PMD, FindBugs, JUnit Test Report, Coverage Report, etc., I feel that the integration of Coverage is relatively difficult, so this article mainly focuses on "how to integrate Junit & Coverage Report in Jenkins" (as for how to write JUnit test, it is not the topic of this article , we assume that the project already supports the JUnit environment).

Regarding the integration of Jenkins, there are not many articles on the Internet. Here is a series of blogs that are more valuable for reference:
http://blog.csdn.net/it_talk/article/details/50267573
If you are interested, you can study it in depth. I will briefly mention some necessary integration steps here, and I will also add some solutions to problems encountered during the integration process.

1. Write coverage.gradle:
I will post the final completion script first, and then explain it separately.
// code coverage plugin
apply plugin: 'jacoco'

android {
// Currently, specifying the version through jacoco{} is no longer supported. If this code is added, the operation will not go wrong, but there will be
// warning prompt
// jacoco{
//        version "0.7.6.201602180812"
//    }

    // The configuration of dexOptions is not required when the project is small, only when the number of project methods exceeds the limit of 64K or
    // The project itself is close to the upper limit of 64K but has not exceeded it. After adding the Junit code, it will exceed (our project belongs to this category
    // situation), you need to do multidex support for the project, the implementation method is very simple, please investigate by yourself.
    // After enabling multidex, you may encounter java.lang.OutOfMemoryError: GC overhead limit exceeded problem, which needs this configuration to solve
    dexOptions {
        javaMaxHeapSize "4g"
    }

    buildTypes {
        debug{
            testCoverageEnabled true
        }
    }
}

//jacocoTestReport depends on the connectedAndroidTest task, so you need to execute connectedAndroidTest before executing jacoco, which means you need to connect to the test machine (emulator or real machine)
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 depends on the test task, so you need to execute test before executing jacoco
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")
}


There are two tasks: jacocoAndroidTestReport, jacocoTestReport. from their respective dependencies
task jacocoAndroidTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest")

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

We can know that jacocoAndroidTestReport is actually for androidTest tests, and jacocoTestReport is for test tests. What is androidTest and test? Simply put, androidTest is the test code that runs on the Android emulator (AVD) or real machine (including Android UI tests), and test is the test code that runs on the jvm. respectively), they also have their own corresponding compiles - "androidTestCompile" and "testCompile", students who are not clear about this part of the knowledge can google it by themselves, in short, select tasks according to the needs of the project.

2. Import coverage.gradle into the project's build.gradle:
apply plugin: 'com.android.library'
// Here is the relative path, assuming coverage.gradle and build.gradle are in the same directory
apply from: './coverage.gradle'

In this way, the local script is completed, and Jenkins needs to be configured next. Jenkins integration coverage actually depends on JAVA, Android, and Gradle environments. If Jenkins is installed locally, these steps may not be required (after all, it is impossible to do development without these environments), but if it is installed on a new remote server, then You may need to set up the above 3 environments in advance and configure the environment variables. I have not actually verified whether these 3 environments are necessary, but when Jenkins runs the gradle script on our remote server, it prompts "can't find project" at the beginning. gradle'", so I configured these environments separately, and then I need to ensure the network of the Jenkins server, otherwise it is very likely that downloading some necessary dependencies during the running of the gradle script will fail.

First, create a new "build" on Jenkins and specify the gradle task to run:

At first, I chose "Invoke Gradle", which requires the gradle environment to be configured on the server, but there is still a problem when I configure it to run, so I changed " Use Gradle Wrapper", Jenkins will use our gradle wrapper to automatically download gradle, here we need to ensure that the network is smooth. "Root Build Script" is to specify your build.gradle path. If you are not sure where the path pointed to by ${workspace} is, you can run it directly, and judge according to the error prompted by the Jenkins log. In short, you must specify the correct build. The gradle path enables Jenkins to find it. The reason why a clean task is added is because after the junit runs once, the next run will report an error if the previous result is not deleted, so clean it once before running.

Then add a "post-build action" and select "Publish Junit test result report":

The path of xml is set to the path of the XML report after JUnit is executed.

Then add a "post-build operation" and select "Record JaCoCo coverage report":

"Path to class directories" configures the path address of the compiled .class file, and Android is placed in the build path build\intermediates\classes; "Path to source directories" configures the java code path.

In this way, Jenkins will execute our task during the build process, generate the junit result report and coverage report, and automatically collect the result and generate the report after the build.

Next, let's analyze coverage.gradle and the problems that may be encountered in the above integration process.

Generally speaking, the integration of coverage can be completed through the following 4 steps:
(1) Introduce the jacoco plug-in;
// code coverage plugin
apply plugin: 'jacoco'

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

(3) Create tasks:
jacocoAndroidTestReport and jacocoTestReport.
(4) Import coverage.gradle into the project's build.gradle:
apply plugin: 'com.android.library'
// Here is the relative path, assuming coverage.gradle and build.gradle are in the same directory
apply from: './coverage.gradle'

In this way, your project should have integrated the coverage function, but if your project is very large and has to be subpackaged (only the tests under androidTest may encounter this problem), you need to do multidex support for the project, and then you You will find that the 64K problem is gone, but you may encounter new problems:
Error:UNEXPECTED TOP-LEVEL ERROR:
java.lang.OutOfMemoryError: GC overhead limit exceeded

At this point, the following settings come in handy:
dexOptions {
        javaMaxHeapSize "4g"
    }

Well, the problem of GC has also been solved, and it can finally run through, but there will still be some warnings in the console. Although it does not affect the construction process, students with obsessive-compulsive disorder are reluctant to see them. In fact, the solution is very simple.

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

Solution: Add the following configuration to the gradle.propertie file
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

The reason for this warning can be found here http://stackoverflow.com/questions/37090135/to-run-dex-in-process-the-gradle-daemon-needs-a-larger-heap-it-currently-has -a , in fact, it is caused by you specifying javaMaxHeapSize "4g" (android studio defaults to 1g)
Solution: add the following configuration to the gradle.propertie file
# Default value: -Xmx1024m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx4608m -XX:MaxPermSize=1024m

The above is the whole process. If you encounter other problems in the integration, please solve it yourself.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326678122&siteId=291194637