Test Code Coverage in a Grails app with Cobertura

January 26, 2008 – 1:18 pm

I’ve always liked having code coverage reports to help me find branches of code that aren’t being tested. On the past couple of Java projects I’ve worked on we used Ant and Cobertura to generate those reports. It turns out that compiled Groovy code can also be instrumented by Cobertura. That got me to thinking about how this could be done on a Grails application.

So I downloaded Cobertura and started writing a Gant script to utilize it in my Grails application. Basically, I wanted to create a wrapper around the TestApp.groovy script that comes with Grails, calling several existing Gant targets to compile and run the tests, with some new targets to instrument the test classes and generate a coverage report. I figured the default target would look something like this:

1
2
3
4
5
6
7
8
9
target ('default': "Test App with Coverage") {
    depends( classpath, checkVersion, configureProxy, packagePlugins )
    cleanup()
    packageApp()
    compileTests()
    instrumentTests() // new target to instrument the classes
    testApp()
    coberturaReport() // new target to generate code coverage reports
}

After a little trial and error, I created a new Gant script (TestAppCoverage.groovy) and got it working. There were a couple workarounds I had to deal with – for example, normally, you fork the test run so that Cobertura can flush the coverage results when the JVM halts. In this case, I didn’t have that luxury so I had to explicitly tell Cobertura to “flush” the coverage results while the JVM was still active (luckily there is an entry at the end of the Cobertura FAQ on how to do this).

Once I had that figured out, the Cobertura Report task couldn’t find the source to display on the HTML report pages. I think Cobertura interprets sub directories as packages, so I couldn’t specify the source directory as ‘grails-app’. Instead, I had to call out each directory individually to provide a ‘flat’ file set to Cobertura, i.e.

1
2
3
4
5
6
7
8
9
10
11
Ant.'cobertura-report'(destDir:"${coverageReportDir}", datafile:"${dataFile}"){
    //load all these dirs independently so the dir structure is flattened,
    //otherwise the source isn't found for the reports
    fileset(dir:"${basedir}/grails-app/controllers")
    fileset(dir:"${basedir}/grails-app/domain")
    fileset(dir:"${basedir}/grails-app/services")
    fileset(dir:"${basedir}/grails-app/taglib")
    fileset(dir:"${basedir}/grails-app/utils")
    fileset(dir:"${basedir}/src/groovy")
    fileset(dir:"${basedir}/src/java")
}

Another issue was where to put the instrumented classes AND have that directory come before the testClasses directory in the classpath. I tried to figure that one out, but eventually decided to reuse the testClasses directory for the instrumented classes. I don’t think this is ideal, but it was easier than figuring out classpath ordering (call me lazy). Also, I had to delete the test classes each test run and re-instrument them in order to get accurate results. This adds some time to the whole process, but new changes didn’t seem to get picked up correctly if I didn’t.

TestApp.groovy calls exit(0) when it finishes – and so the new coberturaReport() target would never get called. Thanks to a tip from Graeme I was able to override the ‘exit’ target in the TestAppCoverage.groovy script:

1
2
3
4
5
6
target('exit':"override exit") { def code ->
    //ignore '0' exit code to bypass TestApp.groovy exiting, process any other return code
    if (0 != code){
        System.exit(code)
    }
}

Finally, I decided that this would actually best be implemented as a plugin, rather than just another script in the ’scripts’ directory of my application. Just download this Grails Cobertrua Plugin zip file (UPDATE: new version released!) to your project directory, then call grails install-plugin grails-cobertura-0.1.zip to install it. Run grails test-app-coverage and check out the reports (reports are placed in test/reports/cobertura).

Give it a shot, and let me know if you have any suggestions on how to improve the TestAppCoverage.groovy script!

  1. 5 Responses to “Test Code Coverage in a Grails app with Cobertura”

  2. I had problems running your plugin:
    1.) pluginHome could not be found so I defined it like this

    pluginHome = new File(“./plugins”).listFiles().find { it.name.startsWith(“cobertura”)}

    2.) testDirPath could not be found, and I don’t know, which directory this could be. Could you please send me a hint?

    By Bernd Schiffer on Jan 28, 2008

  3. These both might be related to the version of Grails you’re running? I developed the plugin using the latest Grails code from SVN, so it’s probably not backwards compatible

    1) The pluginHome variable was just implemented in Grails SVN over the weekend. In the meantime, your workaround will work just fine.

    2) testDirPath is supposed to be where your test classes are compiled to. In Grails 0.6 I think that was {your_app}/test/classes. In the most recent version, Grails sets this to be:

    testDirPath = System.getProperty("grails.project.test.class.dir") ?: "${userHome}/.grails/${grailsVersion}/projects/${baseName}/test-classes"

    By Mike on Jan 28, 2008

  4. Great, works fine now for me. Thank you very much!

    You should set the ‘dependsOn’ variable in CoberturaGrailsPlugin.groovy, so that one with a Grails installation before 1.0-RC4 (like me with my 1.0-RC3 installation) will get a warning that she should upgrade.

    By Bernd Schiffer on Jan 28, 2008

  5. Another issue: I use your plugin on a hudson continuous integration server. Hudson has a plugin for Cobertura Reports, but needs a coverage.xml. You could generate this xml if you add a parameter to the cobertura-report task call like this:

    Ant.’cobertura-report’(destDir:”${coverageReportDir}”, datafile:”${dataFile}”, format:’xml’)

    See the format parameter? This lets your plugin generate the usual html report and a coverage.xml, which can be used by hudson.

    By Bernd Schiffer on Jan 29, 2008

  6. Great job, I look forward to trying this out

    By Todd McGrath on Jan 29, 2008

Sorry, comments for this entry are closed at this time.