Wednesday, May 25, 2022

Maven - finer control of running tests . . .

There should always be something new to learn everyday. Otherwise, life is boring. In software development, it is even more boring if we don't explore to learn anything new.

Maven is popular open source build tool but comes with a price tag of "Time", consumes much of your time - the most precious of all. I've just leaned a better way to take a finer control of running specific unit/integration test. Two years ago, when I was back to Maven-Java world from Gradle-Groovy/Grails world, I had to do quite a bit of exploration. I still end up doing now and then even after a couple of years. Welcome to the Maven world ;)

Environment: Java 18, maven 3.8.5 on macOS Catalina 10.15.7

Surefire and Failsafe are maven plugins used for unit and integration tests. Out-of-the-box, with no additional configuration, you can simply skip running all tests by passing the command line argument to define the property skipTests like :  -DskipTests. This skips all tests, both unit and integration which is also same setting it to true: -DskipTests=true. So, specifying or specifying by setting it with true is same. By default, this property is set with false. So, not specifying at all on the command line, or specifying it with false like: -DskipTests=false is same.

With Surefire and Failsafe plugins and no additional configurations, tests can be run like shown( assuming that we have a multi-module project with my-service-api as a module and running maven commands for the api module from the root project) below:

Run all unit tests:
 ./mvnw -pl my-service-api clean test
Run specific unit test:
 ./mvnw -pl my-service-api clean test -Dtest=MyService1
Run specific unit test method:
 ./mvnw -pl my-service-api clean test -Dtest=MyService1#myServiceTest1

Run all integration tests:
 ./mvnw -pl my-service-api clean integration-test 
Run specific integration test:
 ./mvnw -pl my-service-api clean integration-test -Dit.test=MyServiceIT1
Run Run specific integration test method:
 ./mvnw -pl my-service-api clean integration-test -Dit.test=MyServiceIT1#method1

The problem with this is, running integration test, runs all unit-tests. If you DO NOT want unit tests to be run, when integration test/tests are run, we need to have a finer control to turn off unit tests.

The following explicit surefire and failsafe plugin configuration by three additional properties will give the finer control with the -DskipTests flag intact.

<properties> ... <!-- For finer control of running tests --> <skipTests>false</skipTests> <skipUTs>${skipTests}</skipUTs> <skipITs>${skipTests}</skipITs> </properties> <build> <plugins> ... <!-- surefire for unit tests --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin.version}</version> <dependencies> <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>surefire-junit47</artifactId> <version>${maven-surefire-plugin.version}</version> </dependency> </dependencies> <configuration> <skipTests>${skipUTs}</skipTests> </configuration> </plugin> <!-- failsafe for integration tests --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>${maven-failsafe-plugin.version}</version> <dependencies> <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>surefire-junit47</artifactId> <version>${maven-surefire-plugin.version}</version> </dependency> </dependencies> <executions> <execution> <id>integration-tests</id> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> <configuration> <argLine>${integrationTestCoverageAgent}</argLine> <classesDirectory>${project.build.outputDirectory}</classesDirectory> <includes> <include>**/*IT.java</include> </includes> </configuration> </execution> </executions> <configuration> <skipTests>${skipITs}</skipTests> </configuration> </plugin> ... </plugins> ... </build>

With the above three defined additional properties and set to default: false for both the plugins, all tests run out of the box. Now, with their own separate properties defined for unit-tests and integration-tests, unit tests can be set not to run when we want to run specific integration test or all like:

Run specific integration test, don't run unit tests:
 ./mvnw -pl my-service-api clean integration-test -DskipUTs=true -Dit.test=MyServiceIT1

Run specific integration test, don't run unit tests (same as above, no explicit true):
 ./mvnw -pl my-service-api clean integration-test -DskipUTs -Dit.test=MyServiceIT1

But using verify goal instead of integration-test goal is better. Reason in TIP.

Run specific integration test (BETTER):
 ./mvnw -pl my-service-api clean verify -DskipUTs -Dit.test=MyServiceIT1

Run specific integration test (CLEANER):
 ./mvnw -pl my-service-api clean post-integration-test -DskipUTs -Dit.test=MyServiceIT1

But using post-integration-test goal instead of integration-test goal is cleaner. Reason in TIP.

TIP

Use verify goal instead of integration-test. If you have Docket container started for DB in integration test phase, it will not be stopped after finishing running tests. This will make your subsequent runs fail to start the container. You will running docker command to remove the container (docker rm -f <postgres-image-name>.

The goal: verify is a way to have cleaner way of executing integration test cases in this case.

The goal verify not only runs integration test case(s) but also verifies code coverage for coverage threshold check. The build might result with FAILURE at the end due to coverage threshold not being met. That is ok, we know that we are excluding unit tests and only running partial integration tests and won't expect coverage threshold to be met. But, it will execute cleanly stopping the containers if started any.

The goal: post-integration-test is another cleaner way to execute integration test cases.

Unlike the goal verify, this runs all phases up to post-integration-test (pre-integration-test, integration-test and post-integration-test) leaving out the last verify phase that checks code coverage.  This will execute cleanly stopping the containers if started.

A typical docker container plugin configurations to start before running integration test(s) and stop after running test(s) looks like:

<plugin> <groupId>io.fabric8</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.33.0</version> <executions> <execution> <id>start</id> <phase>pre-integration-test</phase> <goals> <goal>start</goal> </goals> </execution> <execution> <id>stop</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> <configuration> <images> <image> <external> <type>properties</type> <prefix>postgres.docker</prefix> </external> </image> </images> </configuration> </plugin>


References

No comments:

Post a Comment