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); } }
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.
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.