Cobertura de código em projetos Java multi-módulo
Olá! Damos as boas-vindas ao primeiro artigo do nosso time de developers da Konduto! Temos uma equipe apaixonada por tecnologia e apaixonada por aprender, ensinar, compartilhar conhecimento - você já deve ter lido um pouquinho sobre alguns de nós na série Quem faz a Konduto . Pois bem... mas como compartilhar o conhecimento que nasce nas nossas reuniões de dev e nas nossas reuniões de daily? Bom, o Blog da Konduto é o canal de comunicação mais importante da nossa empresa, e por isso pedimos uma licencinha para o pessoal do marketing para, periodicamente, escrevermos alguns artigos para a nossa comunidade de programadores, desenvolvedores e maníacos por tecnologia. O pessoal topou, e agora temos a nossa seção aqui no blog! Espero que gostem!
Introdução
Todos sabem que não tem como ter um código de qualidade sem
testes automatizados. E, para garantir o bom funcionamento da
aplicação, mais importante do que ter testes é ter testes de
qualidade. Como saber que os testes que escrevi para a minha
aplicação são suficientes? Esta é uma longa discussão, já que
podemos olhar para a qualidade de código a partir de vários ângulos
diferentes, desde os requisitos de negócio, passando por aspectos
técnicos e comportamento do usuário. Um destes ângulos é a
cobertura de código. Ou seja, o quanto do seu código está
coberto por testes. Mais especificamente: quantas e quais instruções
da aplicação são visitadas durante os testes. Apenas garantir que o
código está coberto por testes não vai te salvar de todos problemas,
mas já é um primeiro passo em direção a menos bugs.
Para Java existem uma porção de libs que
calculam a cobertura automaticamente para você. O que vamos mostrar
aqui é como configurar cobertura de testes em um projeto Java,
especialmente para o caso em que a aplicação é composta de múltiplos
módulos. Aqui vamos assumir que você está utilizando o Maven como gerenciador de
projeto. Um projeto Java multi-módulo gerenciado pelo Maven possui uma
estrutura de diretórios similar a abaixo:
sample-project/ ├── module1/ │ ├── src/ │ │ ├── main/ │ │ └── test/ │ └── pom.xml ├── module2/ │ ├── src/ │ │ ├── main/ │ │ └── test/ │ └── pom.xml └── pom.xml
Temos a pasta raiz sample-project , ela tem um arquivo Maven pom.xml que descreve o projeto geral. E vemos dois módulos module1 e module2 . Cada um com seu respectivo pom.xml , e onde src/ é guarda o código da aplicação e test/ o código de teste. A cobertura de testes é quanto os testes em test/ cobrem o código contido em src/ . Vale lembrar que nesta estrutura toda dependência e tarefa Maven definida no pom.xml raiz é replicada em todos os módulos por padrão (a não ser que explicitado o contrário). Se você quiser saber mais sobre projetos multi-módulo em Maven, este artigo cobre mais detalhes sobre o assunto. - Para os apressados, colocamos o projeto acima já configurado com cobertura neste repositório .
Calculando a cobertura de código com o JaCoCo
O JaCoCo é um dentre vários sistemas que sabe calcular a cobertura de código de uma suíte de testes Java. Além de te dar a porcentagem do código coberto por testes, ele fornece uma porção de outros indicadores interessantes, como porcentagem das ramificações do código ( if/else , for etc) visitadas, classes, métodos, etc. O primeiro passo é adicionar o plugin do JaCoCo no pom.xml do diretório raiz. É importante que a dependência seja adicionada com escopo test para garantir que ela só seja incluída durante a execução dos testes:
xml
<properties>
<jacoco.version>0.8.3</jacoco.version>
</properties>
<dependencies>
... demais dependências ...
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<type>maven-plugin</type>
<scope>test</scope>
</dependency>
</dependencies>
Em seguida, configuramos para o JaCoCo ser iniciado e executado durante a etapa de build usando a configuração mais básica possível:
xml
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>default-report</id>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
O passo default-prepare-agent inicia o sistema do JaCoCo. O passo default-report executa o JaCoCo para gerar o relatório de cobertura padrão. O JaCoCo possui várias de opções e tipos de relatórios dependendo da sua necessidade. Neste link você encontra a explicação do que é cada uma das configurações possíveis do plugin Maven do JaCoCo. O relatório básico gerado pelo JaCoCo vem em um formato pouco amigável para leitura humana, otimizado para parseamento programático. Para a nossa sorte, o JaCoCo vem com uma função que consegue transformar este relatório em uma página HTML de fácil visualização, contendo todos os indicadores calculados, como porcentagem de instruções/classes/métodos/ramificações cobertas. Para gerar o relatório em HTML, inclua na etapa reporting do pom.xml o plugin do JaCoCo com a seguinte configuração:
xml
<reporting>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<reportSets>
<reportSet>
<reports>
<report>report</report>
</reports>
</reportSet>
</reportSets>
</plugin>
</plugins>
</reporting>
Pronto! Quando você buildar o projeto via mvn install serão gerados os reports de cada módulo nos diretórios seus respectivos diretório target/site/ . Acesse os links abaixo para visualizar os relatórios em HTML:
sample-project/module1/target/site/jacoco/index.html sample-project/module2/target/site/jacoco/index.html
Agregando a cobertura do projeto em relatório único
Você deve ter percebido que, seguindo os passos acima, geramos um relatório para cada módulo. Isso já ajuda, mas não é tão prático para visualizar o projeto todo. Existe um jeito de agregar os relatórios gerados em um único HTML. A partir da versão 0.7.7 , o JaCoCo vem com um método que sabe fazer essa agregação: jacoco:report-aggregate . Quando chamado, este método busca dentro do diretório de todas as dependências do módulo buildado por relatórios de cobertura e os agrega em um relatório único. Perceba que ele só sabe agregar relatórios de módulos listados entre as <dependencies> do projeto. Portanto, a estratégia que vamos usar aqui é criar um módulo especial que tem como dependências todos os demais módulos do projeto. Vamos chamar este módulo de coverage . Para criar este módulo, digite o comando abaixo a partir do diretório raiz do projeto:
mvn archetype:generate \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DarchetypeVersion=RELEASE
Ele vai te pedir alguns inputs, como groupId , artifactId e a versão do módulo. No nosso exemplo demos o nome de coverage ao artifactId . Agora a estrutura de diretórios do seu projeto deve se parecer com a seguinte:
sample-project/ ├── module1/ │ ├── src/ │ └── pom.xml ├── module2/ │ ├── src/ │ └── pom.xml ├── coverage/ │ ├── src/ │ └── pom.xml └── pom.xml
Adicione os demais módulos do projeto como dependências do coverage no coverage/pom.xml :
xml <dependencies> <dependency> <groupId>com.konduto</groupId> <artifactId>module1</artifactId> <version>1.0-SNAPSHOT</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.konduto</groupId> <artifactId>module2</artifactId> <version>1.0-SNAPSHOT</version> <scope>provided</scope> </dependency> </dependencies>
Configure a tarefa jacoco:report-aggregate para rodar durante a etapa build no coverage/pom.xml :
xml
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>report-aggregate</id>
<phase>prepare-package</phase>
<goals>
<goal>report-aggregate</goal>
</goals>
<configuration>
<title>JaCoCo</title>
<footer>Code Coverage Report for JaCoCo ${project.version}</footer>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Pronto. Da próxima vez que você rodar um mvn install no diretório raiz você deve ver o relatório completo de cobertura do projeto no endereço abaixo:
sample-project/coverage/target/site/jacoco-aggreagate/index.html
Sempre que novos módulos forem criados dentro do projeto lembre-se de adicioná-los como dependência do projeto coverage . Além disso, você deve garantir que o módulo coverage seja sempre o último a ser buildado. Ou seja, que na entrada <modules> no pom.xml raiz, o projeto coverage sempre seja último:
xml <modules> <module>module1</module> <module>module2</module> <module>coverage</module> <!-- Deve ser sempre o último --> </modules>
Veja o projeto completo na minha página do Gitlab ! Como foi sua experiência configurando cobertura de código no seu projeto? Você teve algum problema inesperado, ou descobriu algo que pode ter ficado de fora? Este passo a passo só funciona no Maven, sabe como funcionaria no Gradle? Conta para a gente nos comentários como foi, ou entre em contato diretamente com a gente no oi@konduto.com .