Wednesday, December 28, 2011

Classes and Test Jars and Poms Oh My. Building a TestCoverage Test takes them all.

After spending a few days on getting a complicated coverage test working I really feel I should document it.

My setup is that I have 3 maven projects. One parent project and two subprojects, one for functional testing and one for coverage tests. The crux I had with this assignment was that the coverage project needed to be able to read classes and their annotations from it's sister project "xml-functional" to determine what it actually tested.

Overview of the maven project set up

/xmltest
   /xmltest-functional
   /xmltest-coverage


Description
xmltest : This is the parent project, it does nothing in it self.
xmltest-functional : The functional tests
xmltest-coverage : The coverage test what I was building

Creating a Test Jar
First step was to get this working was to set up my xmltest-functional project so it would create a test-jar with all the test classes. For this I used maven-jar-plugin to add a new execution.

In /xmltest/xmltest-functional/pom.xml

<project>
...
  <build>
    <plugins>
    ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.2</version>
        <executions>
          <execution>
            <goals>
              <goal>test-jar</goal>
            </goals>
          </execution>
        </executions>
      </plugin> 
      ...


Did it work?
Now, after running mvn clean install, there is a tests.jar created which contain all the test classes in our functional project.

~/.m2/repository/com/companyx/xmltest/xmltest-functional/2.0.0-SNAPSHOT$ ls -l
xmltest-functional-2.0.0-SNAPSHOT.jar
xmltest-functional-2.0.0-SNAPSHOT.pom
xmltest-functional-2.0.0-SNAPSHOT-tests.jar


So?
And now I can use that jar in our coverage project. First we need to add a dependency to the functional project in our pom. We want to use the "tests.jar" so we set classifier and scope to tests and test.

In /xmltest/xmltest-coverage/pom.xml

...
    <dependency>
      <groupId>com.companyx.xmltest</groupId>
      <artifactId>xmltest-functional</artifactId>
      <version>2.0.0-SNAPSHOT</version>
      <classifier>tests</classifier>
      <scope>test</scope>
    </dependency>


Adding the normal dependency
But that is not all, we also need to add the normal dependency so that when in create class objects of what we find we don't get errors.

In /xmltest/xmltest-coverage/pom.xml

...
    <dependency>
      <groupId>com.companyx.xmltest</groupId>
      <artifactId>xmltest-functional</artifactId>
      <version>2.0.0-SNAPSHOT</version>
    </dependency>


Writing the Test
Now, what we need to do is to write the coverage test.

A typical test class

package com.companyx.xmltest.functional;

...

@Path("/image/{id}")
@Test
public class ImageControllerGetTest {


Coverage Test
Now, finally, I can write a test which scans in the classpath for classes with are annotated with @Test, loops through all that have the correct package, checks if they have the @Path annotation and get the annotation.


ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(Test.class));
      
for (BeanDefinition bd : scanner.findCandidateComponents("com.companyx.xmltest.functional")) {
   
try {
   Class clazz = Class.forName(bd.getBeanClassName());
   if( hasPathAnnotation(clazz) ){
      Annotation annotation = clazz.getAnnotation(Path.class);


And so on and so forth
And you'll just have to imaging that we do the same for the controller classes, puts it all into two lists and compare the lists. But I'm sure you can figure out how to do this :)

Why don't you just filter on Path.class instead of Test.class
I am sure someone would ask this if I actually had any readers.
Good question and the reason we do this is because we want to find test classes which has missed being annotated with @Test. It happens and we want to catch those cases as well.

That's It
I hope you got something out of this, I sure learnt a lot while writing it.
Hope to see you again in the next blog post.

And remeber:

"To error is human, to test is divine"






Maven Ninja Moves

I'll round off with some good commands my Mentor taught me.

mvn versions:display-plugin-updates
Gives you a list of plugs you can update.

mvn versions:display-dependency-updates
Gives you a list of dependencies you can update.