Separando testes unitários de testes integrados usando diretórios distintos com Maven

A qualidade do software está intimamente relacionada à qualidade dos testes empregados sobre ele durante suas mais diversas fases de evolução. Dessa maneira, quanto mais cedo os testes passam a ser elaborados e aplicados, menores são as chances de um sistema gerar “desvios” inesperados.  Isso torna indispensável o fracionamento do tempo de programadores para a elaboração de testes e é por esse motivo que para Uncle Bob:

Desenvolvedor que não testa é como um cirurgião que não lava as mãos.

Durante o processo de desenvolvimento de sistemas, os programadores são essenciais na produção e automatização de duas fases iniciais do processo de testes: os testes unitários e os testes integrados. Afinal, são eles que desenvolvem as entradas e saídas das menores unidade testáveis do programa: as classes e métodos. Além disso, são também eles que entendem como essas unidades se inter-relacionam de maneira a gerar uma determinada funcionalidade. Deve ser, portanto, uma atribuição deles uma correta e eficiente organização dos testes de forma a torná-los facilmente executáveis e diferenciáveis.

Quando se trata de Java, o maven é hoje o framework amplamente difundido, podendo ser considerado um conhecimento básico necessário para o dia-a-dia da programação em Java. Uma das grandes vantagens dele é o gerenciamento simples de dependências (bibliotecas) e a padronização da estrutura do projeto perante o uso de pastas bem definidas, diferenciando código de produção, código de teste, arquivos de recursos como scripts, arquivos de propriedades entre outros conforme a estrutura representada na imagem a seguir:

maven folder structure organization

Enquanto os sistemas muito pequenos ou em estágio embrionário se encaixam muito bem nessa estrutura, sistemas maiores (ou a medida em que os micro-sistemas evoluem) demandam uma separação entre testes rápidos (testes unitários) e testes lentos (testes integrados) e por isso essa preocupação deveria ser pensada desde cedo. Nesse sentido, várias formas distintas foram descritas para permitir essa organização dentro do maven e em geral são alternativas que envolvem a criação de perfis distintos, pasta distintas, configuração de fases no maven ou aplicação de anotações e isso tudo porque o framework é um tanto limitado pois foi planejado para manter testes em uma única pasta: src/test/java.

Neste post quero mostrar uma alternativa que permitirá organizar testes unitários e de integração com o maven em diretórios diferenciados, sem tornar obrigatória a padronização de nomenclatura porém usando anotações conforme a proposta de estrutura a seguir:

folder structure propose for separate testing cycles

Para isso usaremos o recurso de categorização de testes das versões mais recentes do JUnit começando com a criação de uma interface que definirá uma categoria de testes especificas.

public interface TesteDeIntegracao {
}

Com isso, todas as classes que quisermos que se tornem um teste de integração deverão ser anotadas conforme o seguinte exemplo:

@Category(TesteDeIntegracao.class)
public class MeuTeste {
  
  @Test
  public void testName() throws Exception {
    assertTrue(true);
  }

}

Agora precisamos trabalhar a organização dos aquivos e de posse do arquivo pom.xml incluiremos algumas propriedades que usaremos adiante:

<!-- Propriedades de compatibilidade do sistema -->
<project.sourceEncoding>UTF-8</project.sourceEncoding>
<java.version>1.6</java.version>

<!-- Plugins auxiliares -->
<build.helper>1.7</build.helper>
<compiler.plugin>2.5.1</compiler.plugin>
<surefire.plugin>2.12</surefire.plugin>
<failsafe.plugin>2.12</failsafe.plugin>

<!-- Configurações especificas para testes -->
<integration.dir>${basedir}/src/integration-test/java</integration.dir>
<junit.version>4.10</junit.version>

A primeira configuração que devemos fazer é adicionar o diretório ${integration.dir} de forma automática ao nosso build_path e para isso, usaremos o build-helper-maven-plugin que configuraremos da seguinte maneira:

<!-- Adição de novas pastas ao build_path da aplicação -->
<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>build-helper-maven-plugin</artifactId>
  <version>${build.helper}</version>
  <executions>
    <execution>
      <id>default</id>
      <phase>process-test-sources</phase>
      <goals>
        <goal>add-test-source</goal>
      </goals>
      <configuration>
        <sources>
          <source>${integration.dir}</source>
        </sources>
      </configuration>
    </execution>
  </executions>
</plugin>

Em sequência usamos o plugin maven-surefire-plugin. Esse plugin será responsável por executar os testes unitários durante o fase “test” do maven. Nele definimos que tudo o que estiver dentro das pastas “src/test/java” ou “src/integration-test” deve ser testado. Deve ser configurado nessa sessão a exclusão de classes de teste de integração que estão anotadas com a marcação @Configuration(TesteDeIntegracao.class)

<!-- Configuração de testes unitários -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>${surefire.plugin}</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven.surefire</groupId>
      <artifactId>surefire-junit47</artifactId>
      <version>${surefire.plugin}</version>
    </dependency>
  </dependencies>
  <configuration>
    <includes>
      <include>**/*.class</include>
    </includes>
    <excludedGroups>pacoteDaClasse...TesteDeIntegracao</excludedGroups>
  </configuration>
</plugin>

Agora configuraremos o plugin maven-failsafe-plugin para gerenciar a fase integration-test do maven. Esse plugin garante que todos os testes aconteçam mesmo caso um deles falhe o que é um comportamento importante para os testes integrados. Ele também será responsável por filtrar os testes e executar apenas os categorizados com a interface TesteDeIntegracao.

<!-- Configuração de testes integrados -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>${failsafe.plugin}</version>
  <dependencies>
    <dependency>
    <groupId>org.apache.maven.surefire</groupId>
      <artifactId>surefire-junit47</artifactId>
      <version>${failsafe.plugin}</version>
    </dependency>
  </dependencies>
  <configuration>
    <includes>
      <includes>**/*.class</includes>
    </includes>
    <groups>pacoteDaClasse...TesteDeIntegracao</groups>
  </configuration>
</plugin>

Com isso, para executar somente os testes unitários, usaremos o comando:

mvn clean test

E se quisermos somente os testes integrados, usaremos o comando:

mvn clean test-compile failsafe:integration-test

Por fim, para testar tanto testes unitários quanto testes de integração, usamos o comando:

mvn clean verify

Concluíndo, o maven ainda não permite uma fácil diferenciação entre as várias fases de teste mas já há rumores que essa funcionalidade será incluída numa versão futura da ferramenta. Enquanto isso, o exemplo mostrado neste artigo pode ser usado e ampliado para outras fases como testes funcionais por exemplo.

Para uma melhor observação, essa abordagem está disponível para visualização e download no github em:

https://github.com/fpierin/maven-testes-integrados.git

Anúncios