Wednesday Feb 23, 2011

Code coverage using Cobertura

We were requested to investigate and implement code coverage measurement and report generation to our project - Jersey. Nice opportunity to get into new thing and have a motivation to finish (since this was a "must do" type of request..).

Alright, let's get into it. At the begging, we had to decide which tool we are going to use - Cobertura [1] won, one of main arguments are nicer reports, active development and price (it's free).

There is a nice set of scripts, which can instrument classes/jars, collect & merge generated files and finally create report. This can be used in environment, where you have total control of your classpath and can easily run tests against it. Well, Jersey uses maven, so this is little more difficult, but cobertura does have maven support, so lets try it..

Maven support works perfectly when you have tests in each module and nowhere else, otherwise it is probable you'll run into similar issues as I did.. To be more descriptive: let's say, I have module A (which has some tests) and module B, which depends on A (and also has some tests). By default, code coverage results from B will contain only classes from B, which is not correct, because we want to have complete results. I hit this in Jersey, for example substitute A with "jersey-core" and B with "jersey-tests" and you are there. Solution is not that simple, because you have to somehow force B to use instrumented version of A.. which is not very straightforward using maven..

Solution:

You might find or think of few solutions, I like the one described here [2].

Main pom (profile which enables building&local deploying cobertura classified artifacts: 

        <profile>
            <id>cobertura</id>
            <activation>
                <property>
                    <name>cobertura</name>
                </property>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>net.sourceforge.cobertura</groupId>
                    <artifactId>cobertura</artifactId>
                    <optional>true</optional>
                    <version>1.9.4.1</version>
                </dependency>
            </dependencies>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>cobertura-maven-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>cobertura-instrument</id>
                                <phase>pre-integration-test</phase>
                                <goals>
                                    <goal>instrument</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>

                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-jar-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>cobertura-jar</id>
                                <phase>post-integration-test</phase>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                                <configuration>
                                    <classifier>cobertura</classifier>
                                    <classesDirectory>${basedir}/target/generated-classes/cobertura</classesDirectory>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>

                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-install-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>cobertura-install</id>
                                <phase>install</phase>
                                <goals>
                                    <goal>install</goal>
                                </goals>
                                <configuration>
                                    <classifier>cobertura</classifier>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>

and additionally, you need to make sure that instrumented artifacts are used during cobertura:cobertura run. What we have here is two profiles, one activated by -Dcobertura and other one by default:

        <profile>
            <id>cobertura</id>
            <activation>
                <property>
                    <name>cobertura</name>
                </property>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-core</artifactId>
                    <version>${project.version}</version>
                    <classifier>cobertura</classifier>
                </dependency>
            </dependencies>
        </profile>
        <profile>
            <id>default</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-core</artifactId>
                    <version>${project.version}</version>
                </dependency>
            </dependencies>
        </profile>

(this is taken from jersey-server module). I encountered some issues with report generation and actually jersey project structure is not simple, so I couldn't use automatic html report generation in pom file.. So we need to collect all generated cobertura.ser files separately, merge it, prepare all sources and generate report. This can be done by following commands:

# prepare sources
mkdir ../sources
find . | grep src/main/java$ | while read X ; do cp -r "$X"/\* ../sources/ ; done

# compile and run tests
mvn clean install -Dmaven.test.skip=true -Dcobertura
mvn cobertura:cobertura -Dcobertura -DforkMode=never

# collect cobertura.ser files, merge and generate report
find . | grep cobertura.ser$ | while read X  ; do cp "$X" ../cobertura$I.ser ; I=$(($I+1)) ;  done;
cobertura-merge.sh --datafile ./cobertura_final.ser ../cobertura\*
cobertura-report.sh --datafile ./cobertura_filtered.ser --destination ../report --format html ../sources/

Hope it helps somebody who wants to do code coverage for different project. Btw, coverage report for jersey: http://anise.cz/~paja/jersey/report/

[1] http://cobertura.sourceforge.net/
[2] http://foobar.lacoctelera.net/post/2008/09/15/uso-del-plugin-cobertura-dentro-un-proyecto-multi-modulo-en

Friday Feb 11, 2011

Replacing client used in Jersey Test Framework

There was an interesting question on mailing list - Is it possible to replace default Jersey client in JerseyTest? (JerseyTest is a class from Jersey Test Framework, basically superclass of all "Jersey enabled" tests).

Answer is.. not really. It just wasn't made to support this option. Until now :)

There was method getClient(), but it couldn't be easily overriden (it was private static) and it actually does more that create Client, so it isn't really method you want to replace. Long story short.. I created overridable ClientFactory which is (surprisingly) responsible for creating new Clients..

Let's say we want to just replace default Jersey Client implementation by Apache:

public class MainTest extends JerseyTest {
    ...
    @Override
    protected ClientFactory getClientFactory() {
        return new ClientFactory() {
            @Override
            public Client create(ClientConfig clientConfig) {
                return ApacheHttpClient.create(clientConfig);
            }
        };
    }
}

This overrides default client implementation BUT still allows container override this and use its client (this is used for example in InMemoryTestContainer). If you want to have total control and replace even this concept, you can do it by overriding getClient() method:

    @Override
    protected Client getClient(TestContainer tc, AppDescriptor ad) {
        return ApacheHttpClient.create(ad.getClientConfig());
    }

but I don't recommend this approach, former one should be sufficient in most cases.

This functionality is present in 1.6-SNAPSHOT and 1.6-ea03 (and newer Jersey versions).

About

Pavel Bucek

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today