Separating tests in Maven

Filed under: Java, Maven, TDD, Test automation, — Tags: Acceptance test, Integration test, Separation of concern, System tests, Test suit — Thomas Sundberg — 2012-08-21

The way Maven file structure is defined is a great way to separate unit tests and production code. Unit tests are fast. So fast that developers developing using Test Driven Development, TDD, doesn't have any performance problems when applying the Red-Green-Refactor cycle in their work. A large unit test suit is expected to be executed in seconds.

There is, however, no standard on how the files should be structured for slower tests (integration/acceptance/system et.al.). A common property for these tests is that they are slow. They often require a complete system setup in a known state. They use the filesystem, network, database and similar slow resources. Slow tests are normally not executed often. Developers seldom have the patience to wait for a build a long time before writing the next thing. Using them in a Red-Green-Refactor cycle is not practical. It takes too long time.

So what options do we have to separate the fast units tests and the slow tests? There are two main tracks that I have explored.

Separation with name or package conventions

When separating tests based on their names, you either name the test classes after a specific pattern or place them in a specific package. When you want to execute them, you filter the tests based on the pattern you used for the separation.

This option will allow you to write all tests intermingled and execute them at different times. Suppose that we place all our unit test in a package that contains the part unit and all integration tests in a package called integration. Then all that is needed is to set up some filtering in the Maven build. One way to do this is to use include and exclude in Surefire. By excluding a package in the test phase and include it in the integration-test phase, you are able to separate the execution.

An example could look like this:

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>se.waymark.educational</groupId>
    <artifactId>name-separation</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12.2</version>
                <executions>
                    <execution>
                        <id>default-test</id>
                        <configuration>
                            <includes>
                                <include>**/unit/**</include>
                            </includes>
                            <excludes>
                                <exclude>**/integration/**</exclude>
                            </excludes>
                        </configuration>
                    </execution>
                    <execution>
                        <id>integration-test</id>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>test</goal>
                        </goals>
                        <configuration>
                            <includes>
                                <exclude>**/integration/**</exclude>
                            </includes>
                            <excludes>
                                <include>**/unit/**</include>
                            </excludes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

I have hacked the maven-surefire-plugin and defined that it should exclude everything from the integration package in its default execution.

Then I have added an execution to the integration-test phase where the goal test will be executed. The tests in the package unit are now excluded and the tests in the package integration included.

This separation enables me to prepare the system under test before the integration-test phase. If I need I can setup an application server and seed a database before the integration tests are executed.

Executing the build with

mvn integration-test

will execute all tests. Executing it with

mvn test

will only execute the unit tests.

An alternative to this setup is to separate the tests in different modules. Lets explore a setup for module separation.

Separation with a module

A Maven multi module project is needed to be able to separate the test to a specific test module. The parent pom may look like this:

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>se.waymark.educational</groupId>
    <artifactId>module-separation</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>main</module>
        <module>test</module>
    </modules>
</project>

The tests are implemented in the test module. They may be located in any package. The tests in the test module will only be executed in the integration-test phase.

The test module pom may look like this:

test/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>se.waymark.educational</groupId>
    <artifactId>test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12.2</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>integration-test</id>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>test</goal>
                        </goals>
                        <configuration>
                            <skip>false</skip>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>se.waymark.educational</groupId>
            <artifactId>main</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

I do two things here. First I skip all tests in the default execution of Surefire. Then I define that the goal test should be executed in the integration-test phase and that no tests should be skipped.

This separation requires less configuration of Surefire plugin compared to separating the tests using a naming convention.

Execute the build with:

mvn integration-test

This may be done in the project root or in the test module.

I have heard rumours that separating things in different modules is a problem if you are using Eclipse. This may be a valid reason for some people not to separate a project like this. In my opinion, it is not a valid reason. I use better tools than Eclipse and don't have any issues separating a project like this. A better tool is IntelliJ IDEA.

Conclusion

Separating the slow tests in a specific module enables a separation of concern. It also applies the single responsibility principle. The test module is responsible for the slow tests and nothing else.

These are my main argument for preferring separation in different modules instead of separating them using naming conventions.

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