Code coverage evaluation using JaCoCo

Publié dans: 

Introduction

Writing code is a small part of the software development cycle. And while it is important to write good code that runs in the most efficient manner, it is equally important to verify that the resultant software is behaving as expected. To do that, developers write small test cases to make sure that core components don't violate the requirements. But how to be absolutely certain that there are enough tests which cover the whole codebase?

 

Code coverage evaluation is the process in which a test is run to determine the percentage of code that has been run. JaCoCo is a popular Java code coverage tool that can generate coverage reports on the fly using a technique known as Bytecode instrumentation, which modifies the bytecode as it is loaded into memory, to insert tracing calls.

Prerequisite

First of all make sure to download the latest release of JaCoCo from here. Unzip the file then copy jacocoant.jar to the lib directory of your Ant installation. You must already have Ant installed, version below 1.7.0 are not supported.


JaCoCo also works quite well with Maven but I’m only going to cover Ant integration.

Ant integration

Basically you need to add 2 targets to your Ant build file: coverage and report. The former is where you specify your test case (the main class to execute and its parameters) and the latter is for generating a final report document based on data gathered during the first phase.

<target name="dummy-test" depends="compile">
<jacoco:coverage destfile="${result.exec.file}">
  <java classname="org.apache.fop.cli.Main" fork="true">
   <classpath path="${result.classes.dir}" />
   <arg value="value"/>
  </java>
</jacoco:coverage>
</target>

result.exec.file and result.classes.dir are Ant properties which you can either define in your build.xml or use absolute paths instead.

 

JaCoCo will collect information while the JVM is running, then write the result to a binary file that will be later used to generate a nicely formatted report document:

<target name="report" depends="dummy-test">
<jacoco:report>
  <executiondata>
   <file file="${result.exec.file}" />
  </executiondata>
  <structure name="JaCoCo Ant Example">
   <classfiles>
    <fileset dir="${result.classes.dir}" />
   </classfiles>
   <sourcefiles encoding="UTF-8">
    <fileset dir="${src.dir}" />
   </sourcefiles>
  </structure>
  <html destdir="${result.report.dir}" />
</jacoco:report>
</target>

The classfiles tag is mandatory. It must point to a directory containing the generated class files of your project. The sourcefiles tag is optional, if you don’t specify the location of your source code, you won’t be able to see the underlying code of the instrumented methods.


And that’s everything you should know to begin using JaCoCo. Now let’s try a real example.

Apache FOP code coverage

I have arbitrarily chosen Apache FOP to demonstrate JaCoCo since it is the reason I discovered JaCoCo in the first place, and luckily FOP has a complex code base which will make our example slightly interesting. But don’t worry, I won’t delve too much into the specifics of FOP since it is not the aim of this article.

 

You only need to know that FOP is an XSL-FO processor; it takes an XML file as input and generates a paginated document as output (ex. PDF, PS, AFP, etc…).


Checkout FOP into a directory of your choice and make sure that Ant is in your build path.


This is our test target:

<target name="fo2pdf-test" depends="compile-java">
<jacoco:coverage destfile="${jacoco.report.dir}/fop-jacoco-test.exec">
  <java classname="org.apache.fop.cli.Main" fork="true">
   <classpath path="${build.classes.dir}" />
    <arg value="${basedir}/examples/fo/basic/simple.fo"/>
    <arg value="${basedir}/examples/fo/basic/simple.pdf"/>
  </java>
</jacoco:coverage>
</target>

We invoke FOP’s main class along with two parameters to specify the location of the input FO file and a path to save the generated PDF document. We also tell JaCoCo to collect data from the JVM and save it to fop-jacoco-test.exec.


Next we add our usual report target to transform fop-jacoco-test.exec into a well-formatted HTML report.

<target name="coverage-report" depends="test-coverage"
description="Runs JaCoCo for a code coverage report">
<jacoco:report>
  <!-- Set the data file that was generated during the test -->
  <executiondata>
   <file file="${jacoco.report.dir}/fop-jacoco-test.exec"/>
  </executiondata>
  <structure name="Apache FOP">
   <classfiles>
    <fileset dir="${build.classes.dir}"/>
   </classfiles>
   <!-- Source files are optional. If specified Jacoco will highlight
   the code when viewed in the generated report document -->
   <sourcefiles>
    <fileset dir="${src.java.dir}"/>
   </sourcefiles>
  </structure>
  <html destdir="${jacoco.report.dir}"/>
</jacoco:report>
</target>

For the sake of completeness, here is the final XML file:

<?xml version="1.0"?>
<project xmlns:jacoco="antlib:org.jacoco.ant" name="JaCoCo"
default="coverage-report">
  <import file="build.xml"/>
  <property name="jacoco.report.dir" value="${build.dir}/report_jacoco"/>
  <taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
    <classpath location="${jacocoant.jar}"/>
  </taskdef>
  <target name="fo2pdf-test" depends="compile-java">
<jacoco:coverage destfile="${jacoco.report.dir}/fop-jacoco-test.exec">
  <java classname="org.apache.fop.cli.Main" fork="true">
   <classpath path="${build.classes.dir}" />
    <arg value="${basedir}/examples/fo/basic/simple.fo"/>
    <arg value="${basedir}/examples/fo/basic/simple.pdf"/>
  </java>
</jacoco:coverage>
  </target>
  <target name="coverage-report" depends="fo2pdf-test"
    description="Runs JaCoCo for a code coverage report">
    <jacoco:report>
      <executiondata>
        <file file="${jacoco.report.dir}/fop-jacoco-test.exec"/>
      </executiondata>
      <structure name="Apache FOP">
        <classfiles>
          <fileset dir="${build.classes.dir}"/>
        </classfiles>
        <sourcefiles>
          <fileset dir="${src.java.dir}"/>
        </sourcefiles>
      </structure>
      <html destdir="${jacoco.report.dir}"/>
    </jacoco:report>
  </target>
</project>

Save it as jacoco-test.xml in the root directory of your FOP directory then execute the following command:


ant –f jacoco-test.xml coverage-report


If everything went successfully, a report document should be created in build/report_jacoco.


The report document is organized by Java package. For each package, there is a list of HTML documents corresponding to each class within. If you open index.html you should see the list of all packages that were analyzed during the test.


JaCoCo analyzes every instruction in the code and the result is either:

  • Full coverage (green): instruction was fully executed
  • Partial coverage (yellow): one or more branches were missed (ex. If statements)
  • No coverage (red): the instruction was completely missed

Eclipse integration

The Eclipse plugin can be installed from the Marketplace, or by visiting the following URL: http://www.eclemma.org. It makes running code coverage tests much easier and coverage results can be directly displayed in the Eclipse code editor.


Running JaCoCo using the Eclipse plugin is a simple task of clicking a button:

 

JaCoCo Eclipse plugin

 

After execution has finished, coverage results can be directly viewed in the Eclipse code editor by opening any Java file inside your project.

 

JaCoCo Eclipse plugin

Conclusion

Ensuring a minimum quality of code requires two things: first write unit tests to make sure that your code is working as expected, and second verify that these tests cover the majority of the codebase. Reaching full code coverage might be difficult or even unnecessary in certain cases...