Skip to content
 

Cobertura on Google App Engine

Code coverageA good web application should be tested, preferrably with automated integration tests. HtmlUnit is great to write these tests. Measuring the code coverage of these integration tests is as important in my opinion than measuring the code coverage of pure unit tests.

Cobertura is a great tool to measure it. Unfortunately, the usual and only documented ways of getting the code coverage data are

  • to stop the application server, in order for the cobertura shutdown hook to flush its recorded data to a file
  • to remotely call a URL, bound to an action that will flush the recorded data to a file

These two methods don’t work on Google App Engine, because all the applications run in a sandbox where access to the file system is forbidden (even when run locally, on the development server).

There is a solution, though: rewrite the flush action so that it flushes to the HTTP response rather than a file. At the end of the test suite, you just have to remotely invoke the action, through its bound URL, and save the response to a file.

Here’s an example of such an action, using the Stripes framework. Every call to cobertura is implemented using reflection, so that this action compiles even without the cobertura jar files in the classpath.

public Resolution flushCobertura() throws ClassNotFoundException,
                                          SecurityException,
                                          NoSuchMethodException,
                                          IllegalArgumentException,
                                          IllegalAccessException,
                                          InvocationTargetException,
                                          IOException {
    String className = "net.sourceforge.cobertura.coveragedata.ProjectData";
    String methodName = "getGlobalProjectData";
    Class projectDataClass = Class.forName(className);
    Method getGlobalProjectDataMethod =
        projectDataClass.getDeclaredMethod(methodName, new Class[0]);
    Object globalProjectData = getGlobalProjectDataMethod.invoke(null, new Object[0]);
    getContext().getResponse().setContentType("application/octet-stream");
    OutputStream out = getContext().getResponse().getOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(out);
    try {
        oos.writeObject(globalProjectData);
    }
    finally {
        oos.close();
    }
    return null;
}