Cómo configurar tus tests de integración con Maven

En mi anterior post expliqué como configurar los tests de integración con Spring / Spring Boot. Un tema que deje pendiente es como configurar esos tests para ejecutarse aparte de los tests unitarios, y para mantenerlos separados. Y ese es el tema que trataré en este, en concreto, con Maven.

Concepto:
Como ya comente en aquel post, los tests de integración no están pensados para ejecutarse a menudo (a diferencia de los tests unitarios), y lo lógico es ejecutarlos solo en algunos casos en la maquina en la que desarrollamos y sobre todo en nuestro sistema de integración continua.

Por ello lo recomendable respecto a estos tests lo siguiente:

  • Tener los tests de integración en otra paquetería o subcarpeta distinta de los tests unitarios: yo personalmente prefiero en una subcarpeta distinta, pero también los he visto en algún proyecto en la misma subcarpeta (‘test/java’) que los unitarios, pero con alguna paquetería en plan ‘integrationtest.resto.paqueteria’. Para este post lo haré como a mi me gusta más, en una subcarpeta distinta: ‘integration-test/java’. Todo esto se configura fácilmente con Maven.
  • Configurar los tests de integración para ser ejecutados en casos distintos a los unitarios: esto también lo haremos con Maven. Principalmente lo haré creando perfiles distintos en maven, para ejecutar los tests de integración solo cuando estemos con el perfil correspondiente, así como con distintos ‘goals’ de maven.

El proyecto sobre el que voy a mostrar como configurar los tests de integración con Maven es exactamente el mismo que vimos en el post anterior: un sencillo servicio REST sobre un recurso ‘Info’, implementado con capas Controller, Service y Repository (esta última mockeada).

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.5
IDE Intellij 14.0.3 Ultimate version

Pasos:

1. Primero vamos a mover nuestra única clase de test de integración (InfoControllerIT) a su propio subdirectorio de tests de integración. Creamos una subcarpeta bajo ‘src’ que llamaremos ‘integration-test’ y bajo ella otra que llamaremos ‘java’. Para poder crear ahora una paquetería java a partir de esa carpeta, desde Intellij, en File -> Project Structure, seleccionamos la opción ‘Modules’ y ahí podemos marcar como carpeta de tipo ‘Tests’:

Project Structure window

Project Structure window, Intellij

2. Ahora podemos crear la paquetería sobre la nueva carpeta ‘java’. (al ser un test de integración yo le pongo la misma paquetería que si fuera un test del controller, que además es la misma que tenía antes, pero podéis ponerle la que querais). Ahora ya sí, movemos nuestra clase InfoControllerIT a su nuevo directorio y paquetería. Solo con esto ya podemos ejecutar los tests unitarios y de integración separadamente desde Intellij (botón derecho en la subcarpeta ‘java’ de los que queramos lanzar, y ‘Run All Tests’).

3. Pero lo que nos interesa realmente es separarlos a nivel de las ejecuciones maven, así que vamos a configurarlo. Primero, creamos dos perfiles, uno para desarrollo, ‘dev’, (solo tests de unitarios) y otro para tests de integración, ‘integration-test’.

...
    <profiles>
        <profile>
            <id>dev</id>
        </profile>
        <profile>
            <id>integration-test</id>
            <properties>
                <build.profile.id>integration-test</build.profile.id>
                <skip.integration.tests>false</skip.integration.tests>
                <skip.unit.tests>true</skip.unit.tests>
            </properties>
        </profile>
    </profiles>

    <properties>
        ...
        <build.profile.id>dev</build.profile.id>
        <skip.integration.tests>true</skip.integration.tests>
        <skip.unit.tests>false</skip.unit.tests>
    </properties>
...

Con los tag profiles y profile definimos cada perfil de maven. Además, por cada perfil podemos definir unas propiedades. En nuestro caso necesitamos esas propiedades «por defecto» (para ‘dev’), en nuestras properties generales (lineas 19, 20 y 21), y las mismas properties definidas en el perfil ‘integration-test’. Los nombres de las propiedades son bastante autoexplicativos, son booleanos que usaremos para evitar ejecutar unos tests u otros según el perfil.

4. Vamos a añadir el primer plugin de maven que necesitamos: Build Helper Maven Plugin. Sirve para añadir más funcionalidades a los ‘goals’ de maven. En nuestro caso lo vamos a usar para añadir ‘src/integration-test/java’ como otra subcarpeta de tests (por defecto maven solo cuenta como tests los de ‘src/test/java’):

...
<build>
    <plugins>
        ...
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>1.9.1</version>
            <executions>
                <execution>
                    <id>add-integration-test-sources</id>
                    <phase>generate-test-sources</phase>
                    <goals>
                        <goal>add-test-source</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>src/integration-test/java</source>
                        </sources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
    ...
</build>
...

En ‘phase’ le decimos en que fase de maven se debe ejecutar, y en ‘goals’ el goal del build helper plugin que vamos a ejecutar (tiene varios más, en nuestro caso nos interesa el de añadir otra ‘fuente’ de tests, add-test-source). En la configuración del plugin ponemos la nueva carpeta de tests (src/integration-test/java).

5. Añadimos el plugin Maven Surefire Plugin. Este plugin, que de por si ya viene por defecto aunque no lo definamos en el pom, es el que se encarga de la ejecución de los tests unitarios y de generar informes con los resultados. Normalmente no es necesario añadirlo y basta con la configuración por defecto, pero en este caso necesitamos un par de cosas:

...
<build>
    <plugins>
        ...
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.18.1</version>
            <configuration>
                <skipTests>${skip.unit.tests}</skipTests>
                <excludes>
                    <exclude>**/*IT.java</exclude>
                </excludes>
            </configuration>
        </plugin>
        ...
    </plugins>
    ...
</build>
...

Le añadimos en el tag ‘skipTests’ el boolean para evitar los tests unitarios (si somos perfil ‘dev’ se ejecutarán, si somos perfil ‘integration-test’, no.
También le añadimos ciertas clases a excluir, en este caso los .java que terminen en ‘IT’ (asi es como se suelen nombrar los tests de integración). Realmente este plugin ya de por si solo pilla las clases que terminen en ‘Test’, ‘TestCase’ o empiecen por ‘Test’, con lo que esto no es estrictamente necesario, pero yo prefiero tenerlo por si acaso seguimos otro tipo de nomenclatura en los de integración.

6. Por último, añadimos el plugin Maven Failsafe Plugin. Este plugin es el encargado de la ejecución de tests de integración, generando el war/jar aunque los tests fallen. Este no viene por defecto con maven, a diferencia del anterior:

...
<build>
    <plugins>
        ...
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.18.1</version>
            <executions>
                <execution>
                    <id>integration-tests</id>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                    <configuration>
                        <skipTests>${skip.integration.tests}</skipTests>
                    </configuration>
                </execution>
            </executions>
        </plugin>

        ...
    </plugins>
    ...
</build>
...

Con las tags ‘goals’/’goal’ le decimos en que idem de maven ejecutar los tests de integración, y en configuración le ponemos en ‘skipTests’ el booleano para evitar la ejecución de los mismos (si somos perfil ‘dev’ no se ejecutaran, si somos perfil ‘integration-test’, sí)

7. ¡A probar! Ya lo tenemos preparado todo, para probar basta con que desde la tool window de maven de intellij elijamos el perfil (Profiles), ‘dev’ o ‘integration-test’, y ejecutar el goal que queramos. Para ser más concretos, los comandos por consola serían:

  • Para ejecutar los tests unitarios:
    mvn test
  • Para generar el jar (ejecutando tests unitarios):
    mvn package
  • Para ejecutar solo los de integracion, cualquiera de estos dos:
    mvn verify -P integration-test
    mvn integration-test -P integration-test

No es necesario poner que estamos en perfil ‘dev’ en las dos primeras porque es el perfil por defecto, pero podriamos hacerlo, claro.

De esta manera, mientras desarrollemos en local estaremos siempre en perfil ‘dev’, con lo que nunca se ejecutaran los tests de integración. Si en cualquier momento queremos ejecutar los de integración con maven, nos pasaremos a perfil ‘integration-test’.

Tenéis el proyecto completo y ya configurado como siempre en mi github, proyecto integrationtests-maven-example.