Friday, 27 April 2012

Unit and Integration Tests With Maven and JUnit Categories

Introduction

This example shows how to split unit and integration tests using Maven and JUnit categories.
It is especially useful for existing test suites and can be implemented in minutes.

Why use this?

My previous post showed how we to use a maven profile to split unit and integration tests.
http://johndobie.blogspot.co.uk/2011/06/seperating-maven-unit-integration-tests.html
This has been a very well read post and I like how it uses seperate directories. However this example show a much simpler technique that can easily be applied to legacy test suites.
It offers most of the benefits of the original, and sits more comfortably in the Maven world.

Code

The code for the example is here.
svn co https://designbycontract.googlecode.com/svn/trunk/examples/maven/categories
mvn clean install

JUnit Categories

As of JUnit 4.8 you can define your own categories for tests. This enables you to label and group tests.
This example shows how easy it is to separate unit and integration test using the @Catgegory annotation.
http://kentbeck.github.com/junit/javadoc/latest/org/junit/experimental/categories/Categories.html

Define the Marker Interface

The first step in grouping a test using categories is to create a marker interface.
This interface will be used to mark all of the tests that you want to be run as integration tests.
public interface IntegrationTest {}

Mark your test classes

Add the category annotation to the top of your test class. It takes the name of your new interface.
import org.junit.experimental.categories.Category;
@Category(IntegrationTest.class)
public class ExampleIntegrationTest{

 @Test
 public void longRunningServiceTest() throws Exception {
     
 }
}
Categories can be used to mark classes or methods. Really in my opinion you should only mark a class.
If you have both unit and integration tests in a single class then split it.

Configure Maven Unit Tests

The beauty of this solution is that nothing really changes for the unit test side of things.
We simply add some configuration to the maven surefire plugin to make it to ignore any integration tests.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.11</version>
<dependencies>
 <dependency>
  <groupId>org.apache.maven.surefire</groupId>
  <artifactId>surefire-junit47</artifactId>
  <version>2.12</version>
 </dependency>
</dependencies>
<configuration>
 <includes>
  <include>**/*.class</include>
 </includes>
 <excludedGroups>com.test.annotation.type.IntegrationTest</excludedGroups>
</configuration>
</plugin>
There are 2 very important parts. The first is to configure surefire to exclude all of the integrations tests.
<excludedGroups>com.test.annotation.type.IntegrationTest</excludedGroups>
Surefire will run all of your tests, except those marked as an integration test.
The other important part is to make sure the surefire plugin uses the correct JUnit provider. The JUnit47 provider is needed to correctly detect the categories.
<dependencies>
 <dependency>
  <groupId>org.apache.maven.surefire</groupId>
  <artifactId>surefire-junit47</artifactId>
  <version>2.12</version>
 </dependency>
</dependencies>

Running the unit tests

To make sure this works correctly we can run the unit tests
mvn clean test
You can see from the output below that the unit test is run, but not the integration test.
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.test.EmptyUnitTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Configure Maven Integration Tests

Again the configuration for this is very simple.
We use the standard failsafe plugin and configure it to only run the integration tests.
<plugin>
 <artifactid>maven-failsafe-plugin</artifactId>
 <version>2.12</version>
 <dependencies>
  <dependency>
   <groupId>org.apache.maven.surefire</groupId>
   <artifactId>surefire-junit47</artifactId>
   <version>2.12</version>
  </dependency>
 </dependencies>
 <configuration>
  <groups>com.test.annotation.type.IntegrationTest</groups>
 </configuration>
 <executions>
  <execution>
   <goals>
    <goal>integration-test</goal>
   </goals>
   <configuration>
    <includes>
     <include>**/*.class</include>
    </includes>
   </configuration>
  </execution>
 </executions>
</plugin>
The configuration uses a standard execution goal to run the failsafe plugin during the integration-test phase of the build.
The following configuration ensures only the integration tests are run.
com.test.annotation.type.IntegrationTest
And again the JUnit provider must be correctly configured.
<dependencies>
 <dependency>
  <groupId>org.apache.maven.surefire</groupId>
  <artifactId>surefire-junit47</artifactId>
  <version>2.12</version>
 </dependency>
</dependencies>
That’s it!

Running the integration tests

We can now run the whole build.
mvn clean install
This time as well as the unit test running, the integration tests are run during the integration-test phase.
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.test.AnotherEmptyIntegrationTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.016 sec

Running com.test.EmptyIntegrationTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec

Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

Whats next

To see how easy it is to add code coverage to this method, check out this link.
http://johndobie.blogspot.co.uk/2012/05/easy-unit-and-integration-code-coverage.html

For a more complete example which uses starts Tomcat and a database.
svn co https://designbycontract.googlecode.com/svn/trunk/examples/maven/code-coverage
mvn clean install -Ptomcat-embedded
Its based on this example
http://johndobie.blogspot.com/2011/10/maven-integration-testing-and-spring.html

10 comments:

  1. Thanks John, this was exactly what I was looking for!

    One little gotcha though: in the Maven config snippets above <groupId>, <artifactId> and <excludedGroups> should be in mixed-case

    Strangely enough neither Maven nor m2e complained for the lower-case versions, they just silently ignored them....

    ReplyDelete
    Replies
    1. Glad you found it useful and thanks for pointing this out.
      I'll update it very soon.

      All the best.

      John

      Delete
  2. Hi, Thank you John for this useful tutorial :))
    But could we define a maven goal (like test-integration) to run only the integration tests ?

    ReplyDelete
    Replies
    1. Surefire only has the skipTests flag which will cause both the Unit and Integration tests to stop running.

      You can skip just the integration tests by passing -DskipIts for the failsafe plugin. There is currently nothing like that for Surefire.

      You can work around it by defining a profile that excluded all tests.
      Then run mvn clean install -Pskip-unit-tests.

      <profile>
      <id>skip-unit-tests</id>
      <build>
      <plugins>
      <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.11</version>
      <configuration>
      <excludes>
      <exclude>**/*.class</exclude>
      </excludes>
      </configuration>
      </plugin>
      </plugins>
      </build>
      </profile>

      Delete
    2. You could also vote for this new property to be added.

      http://jira.codehaus.org/browse/SUREFIRE-868

      Delete
  3. Nice Article. I want to use Integration test coverage dashboard for QE test case effectiveness. i.e. I want to measure the code coverage, when QE executes manual functional test cases.
    I plan to provide instrumented build to QE engineers and QE engineers will test the application by executing manual test cases. Once the test execution is complete, i want to upload the
    code coverage files to Sonar and would like to see the code coverage data in Integration Test Coverage dashboard.

    Is this possible?

    ReplyDelete
    Replies
    1. Hi, A similar scenario is covered here. You can use a couple of sonar properties to define the file to use for each dashboard widget

      sonar.jacoco.reportPath
      sonar.jacoco.itReportPath

      Good luck.

      http://johndobie.blogspot.com/2012/05/easy-unit-and-integration-code-coverage.html

      Delete
  4. Just a little but important detail: in the code in this page, look at 'artifactid' for example, it's wrong. The ...id... is lowercase; should be: 'artifactId'. It's the same with all the id's. It silly, but if you copy and paste -like all the newbies does, like me :)- it could take several minutes to realise... Thanks for this documente btw.

    ReplyDelete
    Replies
    1. Apologies. That should be ok now.

      Delete
  5. Transparent and clean solution.
    Thank you for this great post!

    ReplyDelete