Maven Integration Tests

I test di integrazione sono una parte molto importante dello sviluppo di un software: permettono di uscire dal piccolo mondo del test di unità per vedere se tutto il puzzle sta in piedi. Maven, nel suo ciclo di vita, riserva addirittura quattro fasi a questo scopo ed un plugin realizzato ad hoc. Vediamo di che si tratta.

Maven Failsafe Plugin

Il plugin classico usato per la fase di test di Maven è chiamato surefire: in questa fase dovrebbero essere eseguiti solamente i test di unità, ovvero test su single classi, micro-funzionalità. Questo tipo di test deve essere veloce, deve dare subito un feedback a chi sviluppa. Sono da evitare quindi accessi alla rete, al file system o al database per esempio. Come li evito? Grazie alla dependency injection e a framework di mocking come EasyMock o Mockito.

I test invece che coinvolgono le altre parti del sistema (altre classi, ma anche servizi web, server vari, database…) sono chiamati test di integrazione. Potremmo stare a discutere ore sulla definizione o la differenza tra test di integrazione, end-to-end e così via: sta di fatto che a Maven non interessa, definisce solo una fase chiamata integration-test, quindi decidiamo la definizione che ci piace di più e implementiamo in questa fase quei test che solitamente sono più lenti ad essere eseguiti perché si avvicinano sempre di più all’architettura finale.
Maven gestisce questo tipo di test con il plugin chiamato failsafe.

Possono essere inclusi in questa parte i test dei servizi REST per esempio, oppure i test dell’interfaccia web con Selenium, oppure i soli servizi che leggono e scrivono dal database: dipende quanto più end-to-end o meno vogliamo che i nostri test siano.

Failsafe in practice

Il plugin failsafe nasce come branch di surefire, dal quale eredita molti parametri di configurazione. Le fasi a cui è legato però sono:

  • pre-integration-test: per il setup della fase di integrazione (avvio del server o cose simili)
  • integration-test: esecuzione vera e propria dei test
  • post-integration-test: chiusura di quanto avviato nella prima fase
  • verify: verifica dei risultati dei test

Il plugin infatti ha questo nome particolare perché normalmente non fa fallire la build se i test non passano a differenza di surefire: infatti se si fermasse la build in fase di integration-test, non verrebbero eseguite le operazioni della post-integration-test, rischiando di lasciare appese un po’ di risorse. Solo nella fase di verify si ha effettivamente la notifica o meno dell’esito dei test. Occhio quindi alle differenze: mentre i test di unità si eseguono con

mvn test

quelli di integrazione verranno eseguiti con il comando

mvn verify

E’ bene non usare direttamente integration-test, altrimenti ci perderemo le ultime due fasi gestite dal plugin!

Detto questo, failsafe non è “gratis” come surefire, ma dobbiamo registrarlo nel nostro pom.xml così:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.18.1</version>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Occhio quindi al goal verify: senza di questo la build non si ferma se i test falliscono!

Pre e Post integration test

Come dicevamo prima, nella fase di pre e post integration-test è possibile preparare l’ambiente di test, come per esempio avviare Tomcat prima dei test e fermarlo dopo. Il plugin di Tomcat per Maven si preoccuperà di fare il deploy della nostra web application:

<plugin>
   <groupId>org.apache.tomcat.maven</groupId>
   <artifactId>tomcat7-maven-plugin</artifactId>
   <executions>
      <execution>
         <id>start-tomcat</id>
         <phase>pre-integration-test</phase>
         <goals>
            <goal>run-war</goal>
         </goals>
         <configuration>
            <fork>true</fork>
         </configuration>
      </execution>
      <execution>
         <id>stop-tomcat</id>
         <phase>post-integration-test</phase>
         <goals>
            <goal>shutdown</goal>
         </goals>
      </execution>
   </executions>
</plugin>

La vera domanda a questo punto è: come differenzio i test di integrazione da quelli di unità? Alla fine sono sempre JUnit test…

failsafe è progettato per considerare test di integrazione quelle classi che soddisfano i seguenti pattern:

  • **/IT*.java
  • **/*IT.java
  • **/*ITCase.java

ovviamente surefire fa il contrario, non le considera test di unità. Su GitHub, il progetto “maven-failsafe-tests” contiene i sorgenti dove sono presenti un po’ di prove effettuate con failsafe: al tag “integration-tests-with-unit-tests” si trova lo stato del progetto con quanto visto fino adesso.

Organizzazione del progetto

Maven di fatto non fa grandi distinzioni tra queste tipologie di test se non per i pattern visti adesso: esiste una proposta di introdurre nello standard la cartella src/it/java proprio per separare i test di unità da quelli di integrazione.

Di fatto, al momento si rischia di creare commistione tra diverse tipologie di test nella cartella src/test/java. Per ovviare a questo possiamo adottare diverse strategie

Cartella separata per i test di integrazione

Possiamo anticipare i tempi e crearci una src/it/java dove raccogliere i test di integrazione. Dal momento che in Maven non posso specificare più di una cartella di test, posso aggirare il problema grazie al build-helper-maven-plugin e specificarla lo stesso:

<plugin>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>build-helper-maven-plugin</artifactId>
   <version>1.5</version>
   <executions>
      <execution>
         <id>add-test-source</id>
         <phase>process-resources</phase>
         <goals>
            <goal>add-test-source</goal>
         </goals>
         <configuration>
            <sources>
               <source>src/it/java</source>
            </sources>
         </configuration>
      </execution>
   </executions>
</plugin>

Fortunatamente anche Eclipse la riconosce e la mostra come source folder (dopo un bel Maven->Update Project).

Possiamo quindi spostare i test in questa cartella: i pattern dei nomi dei file devono rispettare sempre i vincoli visti fino adesso. Non abbiamo quindi libertà sui nomi, ma abbiamo le cose più ordinate. Al tag “integration-tests-it-folder” del progetto su GitHub trovate un esempio d’uso di questa cartella.

Modulo separato

Una strada più radicale è quella di creare un modulo Maven che contiene solo i test di integrazione: gli unici test presenti nella cartella src/test/java quindi saranno quelli di integrazione, soggetti alla solita naming convention. Solitamente è la strada più usata, perché di fatto i test di integrazione investono più moduli di un applicativo. La logica quindi dovrebbe essere questa: ogni modulo contiene i test di unita, quelli di integrazione si guadagnano invece un modulo a sé stante perché di fatto testano più moduli contemporaneamente.

Per progetti di grandi dimensioni ha effettivamente senso questa scelta, altrimenti a mio avviso basta la suddivisione in una cartella separata come src/it/java

Categorie

Esiste infine un’alternativa più “fantasiosa” che permette piena libertà di organizzazione e di naming convention basata sul concetto di categoria.

Le categorie sono state introdotte in JUnit 4.8 e praticamente permettono di raggruppare più test (sia a livello di classe che metodo) insieme, identificati da una “interfaccia-tag“, cioè un’interfaccia senza metodi, che identifica quindi una tipologia di test. Nel nostro caso quindi, potremo creare un’interfaccia per raggruppare i test di integrazione che useremo così:

public interface IntegrationTests {
}

@Category(IntegrationTests.class)
public class ServerInfoTest {

 ...
}

Non ci resta quindi che configurare Maven per far escludere questa categoria da surefire e farla includere in failsafe:

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-surefire-plugin</artifactId>
   <version>2.18.1</version>
   <configuration>
      <excludedGroups>it.cosenonjaviste.failsafe.web.testutils.IntegrationTests</excludedGroups>
   </configuration>
</plugin>
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-failsafe-plugin</artifactId>
   <version>2.18.1</version>
   <configuration>
      <groups>it.cosenonjaviste.failsafe.web.testutils.IntegrationTests</groups>
      <includes>
         <include>**/*.class</include>
      </includes>
   </configuration>
   <executions>
      <execution>
         <goals>
            <goal>integration-test</goal>
            <goal>verify</goal>
         </goals>
      </execution>
   </executions>
</plugin>

Il concetto di categoria JUnit in Maven viene chiamato “gruppo“: nel primo plugin viene escluso il gruppo “IntegrationTests“, nel secondo invece viene incluso. Da notare anche il tag include: failsafe sovrascriverà così la naming convention standard andando a considerare tutte le classi del gruppo specificato, indipendentemente dal nome.

Su GitHub, al tag “integration-tests-with-categories“, è presente un esempio.

Conclusioni

Abbiamo visto come la gestione dei test di integrazione sia supportata da Maven, ma che ancora non abbia una standardizzazione chiara. Ritengo valide tutte e tre le soluzioni: forse l’uso delle categorie può essere fruttato in modo più granulare (magari in congiunzione alle altre due soluzioni) per separare alcuni test molto lenti o strettamente dipendenti da certi ambienti, attivabili da profili Maven. A voi la scelta, le combinazioni sono tante!

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+