The testing landscape has changed since my first jacoco post. A bug was introduced in v22 of the tools. If you are interested you can find the details at https://code.google.com/p/android/issues/detail?id=170607. The gist of the bug is the coverage report file is empty which interferes with JaCoCo working. Basically, it shows you 0% coverage across the board.
The good news is that there is a workaround! You have to create a custom test runner and then everything is golden. Since some the last post is invalid now, I am including the full steps to getting JaCoCo to work for the android tests again.
1. Creating the Workaround Test Runner
- Copy the following class into the androidTest folder:
1:
2: /**
3: * An annoying work around for
4: * <a href="https://code.google.com/p/android/issues/detail?id=170607">issue 170607</a>
5: */
6: public class JacocoTestRunner extends AndroidJUnitRunner {
7:
8: static {
9: System.setProperty("jacoco-agent.destfile",
10: "/data/data/"+BuildConfig.APPLICATION_ID+"/coverage.ec");
11: }
12:
13: @Override
14: public void finish(int resultCode, Bundle results) {
15: try {
16: Class rt = Class.forName("org.jacoco.agent.rt.RT");
17: Method getAgent = rt.getMethod("getAgent");
18: Method dump = getAgent.getReturnType().getMethod("dump", boolean.class);
19: Object agent = getAgent.invoke(null);
20: dump.invoke(agent, false);
21: }
22: catch (Throwable e) {
23: e.printStackTrace();
24: }
25: super.finish(resultCode, results);
26: }
27: }
2. Update Gradle
- Make the following updates to the modules gradle file (the real file can be at build_coverage.gradle):
1: ...
2: apply plugin: "jacoco"
3:
4:
5: android {
6: ...
7: jacoco {
8: version "0.7.1.201405082137"
9: }
10:
11: defaultConfig {
12: ...
13: //workaround for issue 170607.
14: testInstrumentationRunner "com.fsk.mynotes.JacocoTestRunner"
15: //testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16: }
17:
18: buildTypes {
19: debug {
20: testCoverageEnabled = true
21: }
22: }
23:
24: //
25: // dexOptions {
26: // incremental true
27: // }
28: ...
29: }
30:
31: task jacocoTestReport(type: JacocoReport) {
32:
33: def coverageSourceDirs = [
34: 'src/main/java'
35: ]
36:
37: group = "Reporting"
38: description = "Generates Jacoco coverage reports"
39: reports {
40: xml {
41: enabled = true
42: destination "${buildDir}/reports/jacoco/jacoco.xml"
43: }
44: csv.enabled false
45: html {
46: enabled true
47: destination "${buildDir}/jacocoHtml"
48: }
49: }
50:
51: classDirectories = fileTree(
52: dir: 'build/intermediates/classes/debug',
53: excludes: ['**/R.class',
54: '**/R$*.class',
55: '**/BuildConfig.*',
56: '**/Manifest*.*',
57: '**/*Activity*.*',
58: '**/*Fragment*.*'
59: ]
60: )
61:
62: sourceDirectories = files(coverageSourceDirs)
63: additionalSourceDirs = files(coverageSourceDirs)
64: executionData = files('build/outputs/code-coverage/connected/coverage.ec')
65:
66: doFirst {
67: new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
68: if (file.name.contains('$$')) {
69: file.renameTo(file.path.replace('$$', '$'))
70: }
71: }
72: }
73: }
74:
75: dependencies {
76: ...
77:
78: //Unit test dependencies
79: testCompile 'junit:junit:4.12'
80: testCompile 'org.mockito:mockito-core:1.10.19'
81: testCompile 'org.powermock:powermock-module-junit4:1.6.2'
82: testCompile 'org.powermock:powermock-api-mockito:1.6.2'
83:
84: androidTestCompile 'org.hamcrest:hamcrest-library:1.1'
85: androidTestCompile 'com.android.support.test:runner:0.3'
86: androidTestCompile 'com.android.support.test:rules:0.3'
87: androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
88: androidTestCompile "com.google.dexmaker:dexmaker:1.2"
89: androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2"
90: }
- Sync the project to the gradle file. This should happen automatically, but if it doesn’t go to Tools=>Android=>Sync Project with Gradle Files.
3. Running the tests for Coverage
- Start an emulator or a device that can run the tests.
- Open the Android Studio Terminal and enter the following commands:
1: gradlew :MyNotes:createDebugCoverageReport
2: gradlew :MyNotes:jacocoTestReport
(substitute the MyNotes with your module name)
- Assuming that everything succeeded, open the module’s build\jacocoHtml folder.
- Right-Click on the index.html file and open it in a browser.
Caveats
- The androidTests only contain the tests that I either can’t get working as local tests or that will take more work than it is worth. As a result, I have two test categories, Android Tests and Local Tests, that give me coverage for different areas of the code. Unfortunately, this means that I will likely never get a single tool to give me 100% coverage.
- Sometimes the terminal commands will fail or will not produce a coverage report. When that happens just clean the build and repeat the terminal commands.
- I created a special gradle file, build_coverage.gradle, to contain the changes for the coverage. I didn’t want it in my main gradle file because I do not want to deliver the changes in the release version. When I want to run coverage, I copy the special gradle file into the main gradle contents and then revert the change when I am done.
Commits
The majority of the changes can be found on github at https://github.com/fsk-software/mynotes/commit/ed209572c5e1302ac03288a84b4e2003cb5894e7.