Adaptive Test Execution With Gradle And Spring

One of the customizations that we wanted to have as part of our regular build process is to be able to detect which required external services are available in the concrete build execution environment at build time and to adapt the test execution accordingly. This is especially important in regards to the medium and large tests (as defined by google) you might want to execute as part of your build.

Test Classification By Google

It’s medium and large tests that will fail and lead to a broken build in case some of the required external services are not available at build execution time. Having a broken build of this kind actually reflects 2 things:

  1. That your build process, and not necessarily your software package that it’s trying to build, is currently broken.
  2. That the software package that could be built in case you skip the failing medium and large tests possibly does not fulfill all quality requirements that your build process usually requires and should be marked as such accordingly.

We simply wanted to have a build process that is able to adapt according to the execution environment at hand and always provide us with the best possible build under given circumstances. Maybe it’s a beautiful sunny day and you want to work in a park on your notebook where your build execution environment might possibly be limited (your “wellness” environment might likely be improved though 😉 ) or you simply don’t want to have a completely broken build while some other member of your team is trying to get the database online again.

Buildtime Environment Detection

Thanks to the flexibility of Gradle and a single nice feature of Spring TestContext framework creating this kind of adaptive build process was very simple indeed.

The following example will show how you can easily verify whether your database instance (in our case MongoDB) is available at build execution time and how you can adapt your build execution accordingly.

First step is to define a dependency of our build script to a library that will be used in order to verify whether required database instance is currently running:

buildscript {

    dependencies {
        classpath group: 'com.gmongo', name: 'gmongo', version: '0.7'
    }

    repositories {
        mavenCentral()
    }
}

We then define an additional method as part of our build script that will use previously defined library accordingly:


/**
 * Verifies what environment services required for successful test execution are available at buildtime:
 * <ul>
 *     <li>
 *         Verifies whether there is an according MongoDB instance running in the local test environment
 *         listening on the default port of MongoDB: 27017.
 *         Note: GMongo driver initialization fails with IOException wrapped inside of
 *         com.mongodb.MongoInternalException in case there is no running MongoDB instance found.
 *     </li>
 * <ul>
 *
 * @return a Map with according entry for each of the required environment services.
 */
Map defineEnvironmentForTestExecution() {
    def env = new HashMap()
    try {
        new GMongo()
        env.put("CI-MongoDBAvailable", "true")
    } catch (Throwable throwable) {
        env.put("CI-MongoDBAvailable", "false")
    }
    return env
}

Now a Map of identified environment properties has to be included into a Map of system properties defined for each test execution (here defined for multi-module gradle build as part of the subprojects configuration):


subprojects {
   envProps = defineEnvironmentForTestExecution()

    test {
        systemProperties = envProps
    }
}

Additionally, we customize the manifest creation since we want to include the information about the environment that the package has been built in into the MANIFEST file of the package itself (here defined for one of the subprojects being packaged as a WAR archive):


    war {
        manifest {
            attributes envProps
        }
    }

Since envProps has been defined in subprojects section previously, it can be referenced in each of the subprojects accordingly. This will ensure that all information about the environment that the artifact has been created in, will be described in its META-INF/MANIFEST.MF file:

Manifest-Version: 1.0
CI-MongoDBAvailable: false

The only code change that we have to apply in order to get our adaptive test execution that reacts upon the envProps that have been calculated and included into a list of system properties is to annotate our tests with the according @IfProfileValue annotation provided by Spring framework:


@ContextConfiguration(locations = "classpath:WEB-INF/spring-mvc-context.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringRestContextTest {

    @Autowired
    MongoTemplate mongoTemplate;

    @Test
    @IfProfileValue(name = "CI-MongoDBAvailable", value = "true")
    public void testSpringRestContextInitialization() {
        Assert.assertNotNull(mongoTemplate);
    }
}

All tests annotated as described above will now be executed only in case the according MongoDB instance is available in the current build execution environment.

Additionally to providing detailed information about the build environment in the MANIFEST file of the artifact itself, one could easily enough additionally mark the created artifact by modifying its very name to something like $YourArtifactBaseName.PARTIALLY.TESTED.ONLY.war or simply changing its version to 1.0.0.PARTIALLY.TESTED.SNAPSHOT for example.

You could basically mark the artifact in whatever ways you like, given of course that you have already migrated your build process to Gradle ;).

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s