Making life easier with a multi module Maven project

Filed under: Automation, Java, Maven, — Tags: Multi module, Slow tests — Thomas Sundberg — 2014-09-20

Working with slow modules in Maven is a problem. People will not build the module as often as they need and they will therefore not find problems as early as they could.

A solution could be to separate some of the slow stuff to a separate module. One separation can be to have a specific module for slow tests. This will, however, not solve the problem, that the module is too slow.

A solution to the problem could be to only include it in the execution when you invoke a specific Maven profile. This would separate the execution of a slow module from the execution of the rest, fast, modules.

Let me implement a simple example with two modules. There is the first module, the application, that we always want to build. It has fast unit tests and it is therefore not hindering to execute it often. Then there is the second module, the acceptance tests. It requires you to fire up your application before it can be executed. It is therefore dead slow. As a developer you will probably only want to execute the acceptance test module now and then.

Let me show you one way to achieve this.

My solution is to have a Maven profile that includes the acceptance test module. The normal execution doesn't include this slow module, it will only be executed when I explicitly request it.

My parent pom looks like this:

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>se.thinkcode</groupId>
    <artifactId>my-great-application-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>application</module>
    </modules>
    <profiles>
        <profile>
            <id>acceptance</id>
            <modules>
                <module>acceptance-test</module>
            </modules>
        </profile>
    </profiles>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.11</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

It will always do whatever I tell Maven to do with the application module. It will only include the acceptance-test module when I call Maven explicitly with the profile acceptance. In other words, calling Maven like this:

mvn clean install

will perform clean and install on the application module. Invoking Maven like this:

mvn clean install -P acceptance

will perform clean and install on both the application and the acceptance-test module and in that order.

This separation can make the life for the developers better. The developers will be able to iterate faster while developing the application and when they feel that they need it, they can run all the tests and verify that they haven't broken anything. The acceptance tests should only verify things that can't be verified using unit test. The wiring in some applications can be an example.

When should the acceptance profile be executed, then? It should always be executed by your Continuous Integration, CI, server. It is usually you first safetynet and all tests should be executed here as often as possible. Typically a few seconds after each commit. (Or push if you use a modern version control system.)

If you are interested in implementing the entire example, this is the file structure I have used:

example
|-- acceptance-test
|   |-- pom.xml
|   `-- src
|       `-- test
|           `-- java
|               `-- AcceptanceTest.java
|-- application
|   |-- pom.xml
|   `-- src
|       `-- test
|           `-- java
|               `-- UnitTest.java
|-- pom.xml

The implementations looks like this:

application/pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>se.thinkcode</groupId>
        <artifactId>my-great-application-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>application</artifactId>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
    </dependencies>
</project>

application/src/test/java/UnitTest.java

import org.junit.Test;

import static junit.framework.TestCase.assertTrue;

public class UnitTest {
    @Test
    public void shouldAlwaysBeInvoked() {
        assertTrue("Should never fail and will ensure that the unit tests are possible to execute", true);
    }
}

acceptance-test/pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>se.thinkcode</groupId>
        <artifactId>my-great-application-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>acceptance-test</artifactId>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
    </dependencies>
</project>

acceptance-test/src/test/java/AcceptanceTest.java

import org.junit.Test;

import static junit.framework.TestCase.assertTrue;


public class AcceptanceTest {
    @Test
    public void shouldAlwaysBeInvoked() {
        assertTrue("Should never fail and will ensure that the acceptance tests are possible to execute", true);
    }
}

Criticism

It can be argued that this division is bad. It can even be considered to be an anti pattern as described here by Karl-Heinz Marbaise.

I think, however, that the problem pointed out above is a problem with automation. You may end up in an unsynched state if you release manually and forget to release all modules. If you on the other hand always release using your Continuous Integration, CI, server, then it is a lot harder to end up with a mess described above.

As always, with great power comes great responsibility.

The solution that Karl-Heinz Marbaise suggests may be a valid option for you. You will have to decide for yourself what supports your development best.

Conclusion

Running slow test from Maven is possible to do without complicating the life for your developers. You must do whatever it takes to shorten the feedback loop and separating slow tests from fast test is one way to do it.

Resources



(less...)

Pages

About
Events
Why

Categories

Agile
Automation
BDD
Clean code
Continuous delivery
Continuous deployment
Continuous integration
Cucumber
Culture
Design
DevOps
Executable specification
Git
Gradle
Guice
J2EE
JUnit
Java
Javascript
Kubernetes
Linux
Load testing
Maven
Mockito
New developers
Pair programming
PicoContainer
Presentation
Programming
Public speaking
Quality
React
Recruiting
Requirements
Scala
Selenium
Software craftsmanship
Software development
Spring
TDD
Teaching
Technical debt
Test automation
Tools
Web
Windows
eXtreme Programming

Authors

Thomas Sundberg
Adrian Bolboaca

Archives

Meta

rss RSS