Analisando e validando cobertura de código no .NET com Coverlet
Qualidade de código no .NET - Parte 3
Introdução
Testes automatizados são um requisito para garantir a entrega de um produto de qualidade para nossos usuários. Eles ajudam a encontrar bugs e requisitos não cumpridos ainda em tempo de desenvolvimento, mas também diminuem o custo de manutenção fazendo alterações futuras no código mais seguras. Além disso, o ato de escrever código testável, por si só, aumenta a qualidade do código, pois código testável precisa ser desacoplado.
Nesse último post da série, vou mostrar como analisar e validar uma cobertura mínima de código nas nossas aplicações, além de como utilizar testes de integração para aumentar a cobertura do código.
O que é cobertura de código?
Cobertura de código é uma métrica de software que mostra o quanto do nosso código é executado (coberto) por nossos testes automatizados. Ela é expressa em um percentual e pode ser calculada de diferentes formas, baseada na quantidade de linhas ou de branches, por exemplo. Quanto maior o percentual, mais do nosso código está sendo testado.
Analisando a cobertura de código da nossa aplicação
Nesse exemplo, temos uma API ASP.NET Core com um use case simples que verifica se um número é par ou ímpar e retorna uma string:
|
|
Por enquanto, temos apenas um teste de unidade para essa classe:
|
|
Para analisar a cobertura de código da nossa aplicação, primeiro precisamos instalar em nosso projeto de teste o pacote nuget coverlet.msbuild
, que faz a integração do Coverlet com o MSBuild:
Install-Package coverlet.msbuild
Então, executamos o comando dotnet test
com os parâmetros do Coverlet no diretório da nossa solution ou no diretório do projeto da nossa API:
dotnet test -p:CollectCoverage=true -p:CoverletOutputFormat=opencover -p:CoverletOutput=../TestResults
Estamos usando os seguintes parâmetros:
- CollectCoverage: Informa o dotnet test para usar o Coverlet para coletar dados de cobertura de código;
- CoverletOutputFormat: Formato do relatório que será gerado pelo Coverlet (opencover, cobertura, json). Detalhes aqui;
- CoverletOutput: Caminho onde o relatório será gravado. Esse caminho é relativo ao projeto de teste;
O comando irá mostrar uma tabela com a cobertura de código e gerar um arquivo de relatório com o nome TestResults.opencover.xml
:
⚠️ Podemos também executar o Coverlet na linha de comando com o pacote nuget
coverlet.collector
, porém ele possui poucos parâmetros e não imprime os resultados na linha de comando. Mais detalhes aqui;
Gerando relatórios em HTML
O Coverlet gera relatórios em formatos que não são facilmente lidos por humanos, então precisamos gerar um relatório HTML baseado no relatório do Coverlet. Para isso, vamos usar uma ferramenta chamada ReportGenerator.
Instalando o ReportGenerator
O ReportGenerator é instalado como um global tool do .NET.
Para isso, executamos o comando abaixo:
dotnet tool install --global dotnet-reportgenerator-globaltool
Gerando um relatório em HTML a partir de um relatório opencover
Para gerar um relatório em HTML a partir de um relatório do Coverlet, executamos o comando abaixo:
reportgenerator "-reports:TestResults.opencover.xml" "-targetdir:coveragereport" -reporttypes:Html
Estamos usando os seguintes parâmetros:
- reports: Caminho para o relatório do Coverlet;
- targetdir: Caminho onde o relatório HTML será salvo;
- reporttypes: Formato em que o relatório será salvo.
O retorno do comando mostrará o caminho relativo para o relatório gerado: coveragereport\index.html
.
Abrindo o arquivo coveragereport\index.html
nós podemos ver a cobertura de linhas e de branches do projeto:
Clicando em CodeCoverageSample.UseCases.IsEvenUseCase
nós podemos ver detalhes da cobertura de código da classe, cobertura de código por método (na tabela):
Cobertura de linhas vs cobertura de branches
Mas o que é cobertura de linhas e cobertura de branches?
- Cobertura de linhas: Indica o percentual de linhas que estão cobertos pelos testes;
- Cobertura de branches: Indica o percentual de caminhos lógicos que estão cobertos pelos testes (if, else, condições em um switch, etc).
No exemplo abaixo, podemos ver que duas linhas no branch else
não estão sendo cobertas pelos testes.
Isso irá resultar em:
- 50% de cobertura de branches, porque apenas a branch do
if
está coberta; - 71.4% de cobertura de linhas, porque apenas 5 das 7 linhas estão cobertas.
Cobertura de código no Visual Studio
O Visual Studio possui uma extensão chamada Run Coverlet Report que o integra com Coverlet e o ReportGenerator.
- Primeiro, precisamos instalar o pacote nuget
coverlet.collector
nos nossos projetos de teste. Projetos de teste criados com o template do Xunit já vem com esse pacote instalado por padrão.
Install-Package coverlet.collector
- Depois, acesse
Extensions > Manage extensions
e instale a extensãoRun Coverlet Report
.
- Acesse a nova opção
Tools > Run Code Coverage
. O Visual Studio irá gerar e exibir um relatório em HTML.
Além disso, após executar a análise de cobertura de código, o Visual studio irá ler o relatório do coverlet e mostrar a cobertura no nosso fonte:
Aumentando nossa cobertura de código
Corrigindo os testes de unidade
Agora vamos implementar o método NumeroImpar_RetornaImpar
para testar o caminho lógico que não testamos anteriormente:
|
|
Isso irá aumentar nossa cobertura de código para 50% das branches e 22.58% das linhas:
E 100% para a classe IsEvenUseCase
:
Implementando testes de integração
Testes de integração com a classe WebApplicationFactory
(Mais sobre aqui) também são considerados nos relatórios de cobertura de código. Vamos ver a cobertura de código das classes IsEvenController
e Program
:
Agora vamos implementar um teste de integração simples. Ele irá apenas instanciar nossa API e fazer uma chamada passando um número e validando o retorno:
|
|
Agora geramos o relatório de cobertura de código novamente e as classes IsEvenController
e Program
estão cobertas por nossos testes:
Removendo código da análise de cobertura de código
Se quisermos remover uma classe ou método da análise de cobertura de código, podemos decorar ele com o atributo ExcludeFromCodeCoverage
:
|
|
ℹ️ Nós também podemos criar atributos personalizados para excluir código da cobertura de código. Detalhes aqui.
Ignorando auto-properties
O Coverlet possui o parâmetro SkipAutoProps
que permite excluir as auto-properties da análise de cobertura de código.
Por exemplo, essa classe não possui nenhuma lógica e não precisa que os métodos get
e set
das suas propriedades sejam testados:
|
|
Basta passar o parâmetro SkipAutoProps
como true
quando executar o comando para avaliar a cobertura de código:
dotnet test -p:CollectCoverage=true -p:CoverletOutputFormat=opencover -p:CoverletOutput=TestResults -p:SkipAutoProps=true
⚠️ Infelizmente, a extensão Run Coverage Report ainda não permite configurarmos os parâmetros do coverlet. Existe um pull request aberto com essa funcionalidade aguardando aprovação aqui.
Validando uma cobertura mínima na pipeline de build
Assim como com as regras de estilo e qualidade do código, que eu falei no meu post anterior, precisamos validar um percentual mínimo de cobertura de código na nossa pipeline de build para manter o nível de qualidade no nosso código. O Coverlet tem um parâmetro Threshold
onde podemos definir o percentual mínimo para caso esse percentual não seja atingido, a execução dos testes retorne uma falha:
dotnet test -p:CollectCoverage=true -p:CoverletOutputFormat=opencover -p:CoverletOutput=TestResults -p:SkipAutoProps=true -p:Threshold=80
Também podemos passar o parâmetro ThresholdType
para definir qual tipo de cobertura validar. Não especificando, a validação será feita em todos os tipos de cobertura (Linha, Branch e Método). Detalhes aqui.
Referências e links
- https://github.com/coverlet-coverage/coverlet
- https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md
- https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/VSTestIntegration.md
- https://github.com/danielpalme/ReportGenerator
- https://marketplace.visualstudio.com/items?itemName=ChrisDexter.RunCoverletReport
- https://github.com/the-dext/RunCoverletReport/blob/master/README.md