Como testar os meus testes?

Depois de um longo período sem publicar conteúdo, volto com um tema bem interessante!

O título parece errado mas é isso mesmo o que você leu! Seu teste unitário provavelmente está ruim e eu vou tentar provar nas linhas abaixo.

Não vou entrar em detalhes sobre o que é um teste unitário, pirâmide de testes etc porque se você chegou até aqui é porque provavelmente você já escreve teste unitários. Um único ponto que vale a pena abordar é a cobertura de testes de um projeto.

Uma coisa é fato: Escrevemos testes unitários para garantir a qualidade da nossa aplicação, facilitar manutenções futuras (o famoso refactoring), melhorar a escrita de código (TDD) entre muitos outros benefícios.
Geralmente definimos um índice de cobertura de testes mínimo para garantir a qualidade do projeto. Exemplo: 70% de cobertura de testes unitários. Abaixo disso a build quebra (se houver um processo de build e deploy automatizado).

Essa métrica pode ajudar, porém, não garante que os testes unitários foram bem escritos, afinal, a cobertura de testes valida somente se determinada linha de código passou por um teste unitário.

Vou usar um exemplo simples, mas MUITO simples para validar os conceitos que estamos discutindo. O código abaixo concatena duas strings informadas por parâmetro e o seu teste unitário.

public class OperacoesString
{
    public string Concatenar(string t0, string t1)
    {
        return $"{t0}  {t1}";
    }
}

[Fact]
public void Concatenar_ConcatenarDuasStrings()
{
   //Arrange
   string t0 = "Testes com";
   string t1 = "Stryker";

   //Act
   var resultado = new OperacoesString().Concatenar(t0, t1);

   //Assert
   Assert.NotNull(resultado);
}

Se executarmos uma ferramenta para avaliar o índice de cobertura de testes (ex: Sonarqube) vamos obter um índice de 100% de cobertura de testes.

Ok! Meu código está com 100% de cobertura de testes, mas e os meus testes, eles estão realmente bons? Nas próximas linhas mostro como você pode verificar a qualidade do seu teste unitário através dos testes de mutação.

E o que seria um teste de mutação?

Como dito anteriormente, nós geralmente utilizamos a cobertura de linhas de código para medir a qualidade dos testes unitários.
Essa métrica não verifica a qualidade das asserções e uma solução para este problema é utilizar uma ferramenta que altere sua implementação fazendo com o que seu teste falhe.
Em resumo: os testes de mutação permitem que você verifique a qualidade dos testes unitários inserindo bugs temporariamente em seus testes.

Escrever teste já dá um certo trabalho e testar os testes deve ser mais trabalhoso ainda, certo? Eu já entendi que há como verificar a qualidade dos meus testes mas quem pode fazer isso por nós?


Apresento-lhes o Stryker

O Stryker é uma ferramenta de teste de mutação que verifica a qualidade das asserções de testes de unidade. Ele está disponível para C#, JS e Scala.
https://stryker-mutator.io/stryker-net/quickstart

O primeiro passo para utilizar a ferramenta é efetuar a instalação. Neste exemplo vamos fazer uma instalação global utilizando o comando

dotnet tool install –g dotnet-stryker

Também é possível instalar a nível de projeto, o procedimento está disponível em https://stryker-mutator.io/docs/stryker-net/Getting-started

Após a instalação basta navegar via powershell, bash etc até a pasta do projeto de testes e executar o comando de execução do stryker especificando qual projeto deve sofrer mutação caso haja mais de uma camada testada no projeto.

dotnet stryker –project-file=‘C:\<caminho_projeto>\src\TesteMutacao\TesteMutacao.csproj’ –reporters “[‘progress’, ‘html’]”

Os testes forem executados somente em uma única camada não é necessário especificar o projeto basta executar o comando dotnet stryker –reporters “[‘progress’, ‘html’]”

O resultado da execução gera um relatório html onde você visualiza os resultados das mutações. Se um teste sobreviveu é porque o teste não quebrou, afinal o objetivo do teste de mutação é quebrar os seus testes.

Como podemos ver na imagem acima o teste sobreviveu a mutação. Isso significa que algo não está bom em nosso teste. Lembra do índice de cobertura que comentamos acima? Se não efetuarmos os testes com o Stryker nosso projeto estaria com 100% de cobertura de código e mesmo assim não estaria isento de bugs. Então vamos refatorar nosso teste.

[Fact]
public void Concatenar_ConcatenarDuasStrings()
{
   //Arrange
   string t0 = "Testes com";
   string t1 = "Stryker";
   string expected = "Testes com Stryker";

   //Act
   var resultado = new OperacoesString().Concatenar(t0, t1);

   //Assert
   Assert.Equal(expected, resultado);
}

E voilá! Após refatorar o nosso teste não sobreviveu a mutação e foi quebrado (logo não passou no teste para Bruxo 😂🤣)

Este foi um exemplo bem simples de como aplicar testes de mutação utilizando o Stryker. Como podemos ver o esforço nem é muito grande para passar utilizar essa ferramenta e o mais legal é que dá para você adiciona-la ao seu processo de build no AzureDevops, mas isso é assunto para outro post.