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.