Question

I have a Maven project with 4 modules - 3 of them contain code and some tests (testing equals and hashcode of the classes) whereas the 4th module is for testing the 3 other modules.

Now I want to run the cobertura code coverage tool to get an overview which classes are well tested and which are not. I did some investigations on that topic and it seems that cobertura is not aware of generating the right code coverage percentages and line coverages, if some sources which are tested are located within other modules.

I have read over some links like SeamTestCoverageWithCobertura and Using the plugin Coverage within a multi-module Maven 2 but there has to be an out of the box solution. Can anybody report some new directions on this topic? Or are there bether tools like cobertura? I've stumbled upon emma but this tool does not offer line coverage...

Was it helpful?

Solution

As of version 2.6, there is an aggregate option that can be set to true in the parent pom:

<reporting>
<plugins>
  <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>cobertura-maven-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <outputDirectory>./target/tmpCobertura</outputDirectory>
        <formats>
            <format>html</format>
        </formats>
        <aggregate>true</aggregate>
    </configuration>
  </plugin>
</plugins>
</reporting>

OTHER TIPS

We don't have sonar here and right now, we cant install it. So i had to find a workaround and got one. This solution works with a simple mvn clean install -DrunCobertura=true in a multi module project. You only need to add this profile to your super pom.xml of your project, define the working.dir property and it should work.

<profile>
    <id>runCobertura</id>
    <activation>
        <property>
            <name>runCobertura</name>
            <value>true</value>
        </property>
    </activation>
    <properties>
        <cobertura.format>html</cobertura.format>
        <cobertura.working.dir>${working.dir}/${project.version}/cobertura</cobertura.working.dir>
        <cobertura.complete.ser.file>${cobertura.working.dir}/complete.ser</cobertura.complete.ser.file>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-clean-plugin</artifactId>
                <version>2.4.1</version>
                <inherited>false</inherited>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>.</directory>
                            <includes>
                                <include>cobertura.ser</include>
                            </includes>
                        </fileset>
                        <fileset>
                                <directory>${cobertura.working.dir}</directory>
                            </fileset>
                    </filesets>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
                <version>1.7</version>
                <executions>
                    <execution>
                        <id>cobertura-Instrument</id>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <target>
                                <taskdef resource="tasks.properties"/>
                                <taskdef resource="net/sf/antcontrib/antcontrib.properties"/>
                                <if>
                                    <available file="${project.build.outputDirectory}"/>
                                    <then>
                                        <cobertura-instrument>
                                            <fileset dir="${project.build.outputDirectory}">
                                                <include name="**/*.class"/>
                                            </fileset>
                                        </cobertura-instrument>
                                    </then>
                                </if>
                            </target>
                        </configuration>
                    </execution>
                    <execution>
                        <id>cobertura-createCombinedSerFile</id>
                        <phase>generate-test-sources</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <target>
                                <taskdef resource="tasks.properties"/>
                                <taskdef resource="net/sf/antcontrib/antcontrib.properties"/>
                                <if>
                                    <available file="${cobertura.complete.ser.file}"/>
                                    <then>
                                        <cobertura-merge datafile="${basedir}/tmp.ser">
                                            <fileset file="${cobertura.complete.ser.file}"/>
                                            <fileset file="${basedir}/cobertura.ser"/>
                                        </cobertura-merge>
                                        <move file="${basedir}/tmp.ser" tofile="${basedir}/cobertura.ser"/>
                                    </then>
                                </if>
                            </target>
                        </configuration>
                    </execution>
                    <execution>
                        <id>cobertura-copyResultSerFileAndSources</id>
                        <phase>test</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <target>
                                <taskdef resource="tasks.properties"/>
                                <taskdef resource="net/sf/antcontrib/antcontrib.properties"/>
                                <if>
                                    <available file="${basedir}/cobertura.ser"/>
                                    <then>
                                        <move file="${basedir}/cobertura.ser" tofile="${cobertura.complete.ser.file}"/>
                                        <mkdir dir="${cobertura.working.dir}/source"/>
                                        <if>
                                            <available file="${basedir}/src/main/java"/>
                                            <then>
                                                <copy todir="${cobertura.working.dir}/source">
                                                    <fileset dir="src/main/java">
                                                        <include name="**/*.java"/>
                                                    </fileset>
                                                </copy>
                                            </then>
                                        </if>
                                        <cobertura-report datafile="${cobertura.complete.ser.file}" format="${cobertura.format}" destdir="${cobertura.working.dir}/report">
                                            <fileset dir="${cobertura.working.dir}/source"/>
                                        </cobertura-report>
                                    </then>
                                </if>
                            </target>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>net.sourceforge.cobertura</groupId>
                        <artifactId>cobertura</artifactId>
                        <version>1.9.4.1</version>
                    </dependency>
                    <dependency>
                        <groupId>ant-contrib</groupId>
                        <artifactId>ant-contrib</artifactId>
                        <version>20020829</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>net.sourceforge.cobertura</groupId>
            <artifactId>cobertura</artifactId>
            <version>1.9.4.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</profile>

What does it do:

1. @process-classes-Instrument the compiled classes of the module.

2. @generate-test-sources-Merges the .ser file from previous modules with the created one of this module to get the complete code coverage.

3. @test-Creates the code coverage report. Should be called in the last module, but due to the fact that the last module can change, i call it always and the previous reports will be overwritten. If you use the report in xml format (for Jenkins) it's fast, so it doesn't matter.

According to MCOBERTURA-65, the maven cobertura plugin still doesn't know how to aggregate reports of sub-modules into a consolidated one. Some work has been done to implement a merge target on the maven cobertura plugin (see MCOBERTURA-33) but this code hasn't been included in the plugin yet. I didn't test the patch myself and can't say if it's worth a try.

In consequence, lots of people indeed suggest to use the maven dashboard plugin but I'd personally stay far away from it as it isn't very satisfying on the long term and I faced lots of issues with it (technical problems, lost of history,...). Instead, I warmly recommend Sonar. Have a look at Nemo, a public instance of the last version of Sonar, for a live demo of this tool. See for example the Commons Digester project and the drill down of code coverage.

There are a few plugins that aggregate Cobertura (and other) reports. Check out the sonar and XRadar plugins. There is also the dashboard plugin, but it is a bit clunky.

FWIW Emma does do line coverage.

I would really like to thank Sven Oppermann for submitting his runCobertura profile solution. This helped me solve the question of 'how do you get aggregate coverage reports for multi-module projects when you might not be able to use Sonar.

I have created an example which demonstrates how to create multi-module projects which produce code coverage reports that assess not only unit test coverage (in all submodules), but also coverage on INTEGRATION TESTS THAT BRING UP YOUR APPLICATION AS A .WAR IN JETTY. The example is hosted here:

        http://dl.dropbox.com/u/9940067/code/multi-module-cobertura.zip 

The recipe I am providing is pretty re-useable if you copy the runCobertura profile listed below (based on the one provide by Sven.)

Here are some notes which will help you use this profile:

  • the integration test module that launches jetty (and defines tests that run against the production .war) must either be named web-test-driver-for-code-coverage, or you must modify the statements in the runCobertura configuration block.

  • your coverage reports will appear wherever you set your variable

  • you MUST include 'clean' on the command line when you run your build for code coverage. 'clean' will blow away prior cobertura.ser files, which if left lurking around can cause very confusing reports to be generated (a sign you need to 'clean' is that the reports show 100% coverage for everything, including stuff you know is never called.

      mvn -PrunCobertura clean install      # gives you the aggregate reports.
    
  • the module web-test-driver-for-code-coverage defines a servlet context listener that explicitly flushes the cobertura metrics to disk when the web server shuts down. Supposedly the container is supposed to do this automatically, but that didn't work for me, so I had to hook in the explicit call to flush out the metrics.

  • the integration tests are done in groovy because i based this on some maven project skeletons that already used groovy. Sorry for the added clutter, but it does show you how to do your tests in groovy (which is highly recommended anyway.)

  • Note that when you compile with the runCobertura profile all of your artifacts are created with cobertura instrumentation, even your .war file. You NEVER want to let this get out in production of course (for one thing it would run realllll slow.) I have not yet figured out a food way to get the artifacts to rename themselves so that the 'cobertura-ness' is obvious.

    <profiles>
    <profile>
        <id>runCobertura</id>
        <activation>
            <property>
                <name>runCobertura</name>
                <value>true</value>
            </property>
        </activation>
        <properties>
            <cobertura.format>html</cobertura.format>
            <working.dir>/tmp</working.dir>
            <cobertura.working.dir>${working.dir}/${project.version}/cobertura</cobertura.working.dir>
            <cobertura.complete.ser.file>${cobertura.working.dir}/complete.ser</cobertura.complete.ser.file>
    
            <!-- scope which determines whether or not cobertura is included in .war file: overriden here -->
            <cobertura.dependency.scope>compile</cobertura.dependency.scope>
        </properties>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>2.4.1</version>
                    <inherited>false</inherited>
                    <configuration>
                        <filesets>
                            <fileset>
                                <directory>.</directory>
                                <includes>
                                    <include>**/cobertura.ser</include>
                                </includes>
                            </fileset>
                            <fileset>
                                    <directory>${cobertura.working.dir}</directory>
                                </fileset>
                        </filesets>
                    </configuration>
                </plugin>
    
    
    
    
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <version>1.7</version>
                    <executions>
                        <execution>
                            <id>cobertura-Instrument</id>
                            <phase>process-classes</phase>
                            <goals>
                                <goal>run</goal>
                            </goals>
                            <configuration>
                                <target>
                                    <taskdef resource="tasks.properties"/>
                                    <taskdef resource="net/sf/antcontrib/antcontrib.properties"/>
                                    <echo message="::PROCESS CLASSES: ${artifactId}"/>
    
                                    <if>
                                      <equals arg1="${artifactId}" arg2="web-test-driver-for-code-coverage" />
                                        <then>
                                            <echo message="::SKIPPING PHASE for integration test"/>
                                        </then>
                                        <else>
                                            <if>
                                                <available file="${project.build.outputDirectory}"/>
                                                <then>
                                                    <echo message="::BEFORE INSTRUMENT"/>
                                                    <cobertura-instrument>
                                                        <fileset dir="${project.build.outputDirectory}">
                                                            <include name="**/*.class"/>
                                                        </fileset>
                                                    </cobertura-instrument>
                                                </then>
                                            </if>
                                        </else>
                                    </if>
    
    
                                </target>
                            </configuration>
                        </execution>
                        <execution>
                            <id>cobertura-createCombinedSerFile</id>
                            <phase>generate-test-sources</phase>
                            <goals>
                                <goal>run</goal>
                            </goals>
                            <configuration>
                                <target>
                                    <taskdef resource="tasks.properties"/>
                                    <taskdef resource="net/sf/antcontrib/antcontrib.properties"/>
                                    <echo message=":::generate-test-sources"/>
    
    
                                    <if>
                                      <equals arg1="${artifactId}" arg2="web-test-driver-for-code-coverage" />
                                        <then>
                                            <echo message="::SHORT CIRCUIT COMBINE PHASE for integration test"/>
                                            <echo  message="source - ${cobertura.complete.ser.file} dest - ${basedir}/cobertura.ser"/>
                                            <copy file="${cobertura.complete.ser.file}" tofile="${basedir}/cobertura.ser"/>
                                        </then>
                                        <else>
                                            <if>
                                                <available file="${basedir}/cobertura.ser"/>
                                                <then>
                                                    <echo message="::: Is available ${basedir}/cobertura.ser"/>
                                                </then>
                                            </if>
    
                                            <if>
                                                <available file="${cobertura.complete.ser.file}"/>
                                                <then>
                                                    <echo message="before merge1"/>
                                                    <cobertura-merge datafile="${basedir}/tmp.ser">
                                                        <fileset file="${cobertura.complete.ser.file}"/>
                                                        <fileset file="${basedir}/cobertura.ser"/>
                                                    </cobertura-merge>
                                                    <echo message="move temp.ser to ${basedir}/cobertura.ser"/>
                                                    <move file="${basedir}/tmp.ser" tofile="${basedir}/cobertura.ser"/>
                                                </then>
                                            </if>
                                        </else>
                                    </if>
                                </target>
                            </configuration>
                        </execution>
                        <execution>
                            <id>cobertura-copyResultSerFileAndSources</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>run</goal>
                            </goals>
                            <configuration>
                                <target>
                                    <taskdef resource="tasks.properties"/>
                                    <taskdef resource="net/sf/antcontrib/antcontrib.properties"/>
    
                                    <echo message=":::copyResultSerFileAndSources -beforeIf"/>
                                    <if>
                                        <available file="${basedir}/cobertura.ser"/>
                                        <then>
                                            <echo message="move1"/>
                                            <move file="${basedir}/cobertura.ser" tofile="${cobertura.complete.ser.file}"/>
                                            <mkdir dir="${cobertura.working.dir}/source"/>
                                            <if>
                                                <available file="${basedir}/src/main/java"/>
                                                <then>
                                                    <copy todir="${cobertura.working.dir}/source">
                                                        <fileset dir="src/main/java">
                                                            <include name="**/*.java"/>
                                                        </fileset>
                                                    </copy>
                                                </then>
                                            </if>
                                            <echo message="runreport"/>
                                            <cobertura-report datafile="${cobertura.complete.ser.file}" format="${cobertura.format}" destdir="${cobertura.working.dir}/report">
                                                <fileset dir="${cobertura.working.dir}/source"/>
                                            </cobertura-report>
                                        </then>
                                    </if>
                                </target>
                            </configuration>
                        </execution>
                    </executions>
                    <dependencies>
                        <dependency>
                            <groupId>net.sourceforge.cobertura</groupId>
                            <artifactId>cobertura</artifactId>
                            <version>1.9.4.1</version>
                        </dependency>
                        <dependency>
                            <groupId>ant-contrib</groupId>
                            <artifactId>ant-contrib</artifactId>
                            <version>20020829</version>
                        </dependency>
                    </dependencies>
                </plugin>
            </plugins>
        </build>
        <dependencies>
            <dependency>
                <groupId>net.sourceforge.cobertura</groupId>
                <artifactId>cobertura</artifactId>
                <version>1.9.4.1</version>
            </dependency>
        </dependencies>
    </profile>
    </profiles>
    

Thomas Sundberg offers an interesting solution in which instrumentation and test reporting is done via ant, but all testing and dependency management via mvn.

Check Here: thomassundberg wordpress

It means that you have to execute the commands below at the parent level in this order:

mvn clean compile
ant instrument
mvn test
ant report

Integrating these steps into sonar is described by Martijn Stelinga.

test-coverage-in-multi-module-projects

I could implement something quite similar to what you need thanks to this answer: Maven - add dependency on artifact source

I just added <classifier>sources</classifier> and cobertura includes classes from dependencies as well.

Regards.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top