Reusable Test Utility Code and Maven

I often see almost identical bits of code copied and pasted in test classes spread throughout different packages, even different modules. Sometimes someone will bother to create a reusable utility class that is called from different tests, but then someone else will discover that they need it in a different module and lazily copy the entire class.

So now we have copy-paste duplication not just of code fragments but of entire classes. Then someone changes the way that code behaves but doesn’t spot the copy, and we end up with two diverging mechanisms with subtle differences. 6 months later someone has an annoying little obstacle to resolve before they can do their piece of work.

The solution is simple. Reusable test utility code that needs to be re-used in a different module should be exported in a test-scoped test utility jar.

First you must carefully consider what “reusable test utility code” really means and what you are doing with your module dependencies, but lets assume that you do have properly reusable independent utility code and a “compile” scope module dependency already exists.

What is a sensible candidate for test utility code? Custom Hamcrest matchers, JUnit Rules, code that works with dates and times, collections, or protocols, standards and formats such as HTTP, or SSL, or XML or SOAP and other things that aren’t tied to some internal domain classes. Remember that a JUnit Rule can be a great way to achieve reuse of something that needs to happen in both set-up and tear-down.

Step 1 – Export the test code in a test jar

You’re going to export test code in a test jar, distinct from the source jar. Since other modules should have no interest in the unit test classes, you only want to export your reusable test utility classes. I’ve found that the easiest way to handle that is to adopt a convention that all test utility code lives in a package with “testutils” somewhere in its name, so that you can set up a standard filter pattern.

Another option is to create a separate source directory eg src/testutils, but I’ve found odd problems in Eclipse when using more than one additional src path (eg src, src/systemtest and src/testutils), especially with m2e.

Exporting a test jar then needs maven config like this:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>test-jar</goal>
      </goals>
      <configuration>
        <includes>
          <include>**/testutils/**</include>
        </includes>
      </configuration>
    </execution>
  </executions>
</plugin>

That config should go in the parent pom and then any module can use it with this short form:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <executions>
    <execution>
      <id>build-test-jar</id>
      <phase>package</phase>
    </execution>
  </executions>
</plugin>

Step 2 – Import the test jar dependency in test scope

The importing module uses a dependency entry with the appropriate type and scope elements, like this:

<dependency>
  <groupId>some.thing</groupId>
  <artifactId>SomeThing</artifactId>
  <version>${some.version}</version>
  <type>test-jar</type>
  <scope>test</scope>
</dependency>

Step 3 – Don’t accidentally stop it working

If you build using -Dmvn.test.skip then the test utility code won’t be compiled and packaged. To skip test execution but still allow the test jar to be built, packaged and exported, use -DskipTests.

Advertisements
Posted in Clean Test Code, Maven

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: