Featured image of post Analisando e validando cobertura de código no .NET com Coverlet

Analisando e validando cobertura de código no .NET com Coverlet

Qualidade de código no .NET - Parte 3

Follow me
Esse post é parte de uma série:
Parte  3  -  Analisando e validando cobertura de código no .NET com Coverlet

Introdução Link to this section

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? Link to this section

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 Link to this section

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
using CodeCoverageSample.Interfaces;

namespace CodeCoverageSample.UseCases;

public class IsEvenUseCase : IIsEvenUseCase
{
    public string IsEven(int number)
    {
        if (number % 2 == 0)
        {
            return "Número é par";
        }
        else
        {
            return "Número é ímpar";
        }
    }
}

Por enquanto, temos apenas um teste de unidade para essa classe:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
using CodeCoverageSample.UseCases;

namespace CodeCoverageSample.UnitTests;

public class IsEvenUseCaseTests
{
    [Fact]
    public void NumeroPar_RetornaPar()
    {
        //Arrange
        var isEvenUseCase = new IsEvenUseCase();

        //Act
        var result = isEvenUseCase.IsEven(2);

        //Assert
        Assert.Equal("Número é par", result);
    }
}

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:

Retorno da cobertura de código na linha de comando

⚠️ 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 Link to this section

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 Link to this section

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 Link to this section

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.

Retorno do ReportGenerator

Abrindo o arquivo coveragereport\index.html nós podemos ver a cobertura de linhas e de branches do projeto:

Resumo no relatório em HTML

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):

Detalhes da classe no relatório em HTML

Cobertura de linhas vs cobertura de branches Link to this section

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 Link to this section

O Visual Studio possui uma extensão chamada Run Coverlet Report que o integra com Coverlet e o ReportGenerator.

  1. 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
  1. Depois, acesse Extensions > Manage extensions e instale a extensão Run Coverlet Report.

  1. 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 Link to this section

Corrigindo os testes de unidade Link to this section

Agora vamos implementar o método NumeroImpar_RetornaImpar para testar o caminho lógico que não testamos anteriormente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using CodeCoverageSample.UseCases;

namespace CodeCoverageSample.UnitTests;

public class IsEvenUseCaseTests
{
    [Fact]
    public void NumeroPar_RetornaPar()
    {
        //Arrange
        var isEvenUseCase = new IsEvenUseCase();

        //Act
        var result = isEvenUseCase.IsEven(2);

        //Assert
        Assert.Equal("Número é par", result);
    }

    [Fact]
    public void NumeroImpar_RetornaImpar()
    {
        //Arrange
        var isEvenUseCase = new IsEvenUseCase();

        //Act
        var result = isEvenUseCase.IsEven(3);

        //Assert
        Assert.Equal("Número é ímpar", result);
    }
}

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 Link to this section

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using Microsoft.AspNetCore.Mvc.Testing;
using System.Net;

namespace CodeCoverageSample.UnitTests.IntegrationTests;

public class IsEvenIntegrationTest : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public IsEvenIntegrationTest(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData(2, "Número é par")]
    [InlineData(3, "Número é ímpar")]
    public async Task Numero_RetornaCorretoEOk(int number, string expectedResult)
    {
        var HttpClient = _factory
            .CreateClient();

        //Act
        var HttpResponse = await HttpClient.GetAsync($"/iseven/{number}");

        //Assert
        Assert.Equal(HttpStatusCode.OK, HttpResponse.StatusCode);

        var ResponseStr = await HttpResponse.Content.ReadAsStringAsync();

        Assert.Equal(expectedResult, ResponseStr);
    }
}

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 Link to this section

Se quisermos remover uma classe ou método da análise de cobertura de código, podemos decorar ele com o atributo ExcludeFromCodeCoverage:

1
2
3
4
5
6
7
8
9
using System.Diagnostics.CodeAnalysis;

namespace CodeCoverageSample;

[ExcludeFromCodeCoverage]
public class NaoMeTeste
{
    ...
}

ℹ️ Nós também podemos criar atributos personalizados para excluir código da cobertura de código. Detalhes aqui.

Ignorando auto-properties Link to this section

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:

1
2
3
4
5
6
7
namespace CodeCoverageSample;

public class SemLogicaAqui
{
    public int Id { get; set; }
    public int Nome { get; set; }
}

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 Link to this section

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.

Exemplo de um teste com falha com o parâmetro Threshold=80

💬 Like or have something to add? Leave a comment below.
Ko-fi
GitHub Sponsor
Licensed under CC BY-NC-SA 4.0
Criado com Hugo
Tema Stack desenvolvido por Jimmy