Desbloquea o poder dos testes em ASP.NET: aprimora a qualidade do software: xUnit e Coverlet II
Testes e cobertura de testes com xUnit e Coverlet — Parte II
Testes de software são cruciais para garantir a entrega de um produto de boa qualidade, oferecem adicionalmente um conforto na fase de manutenção, refatoração de código ou mesmo na mudança de elementos da equipa, além de outros benefícios elencados na parte 1.
Conforme avançado na primeira parte, vamos criar um projecto com o respectivo projecto de teste com xUnit e Coverlet.
Com vista dar corpo ao exemplo, vamos criar uma pasta para os projectos, na pasta vamos ter uma class library e o projeto de teste.
mkdir xUnitCoverlet
cd xUnitCoverlet
Código a ser testado
Na class library abaixo, vamos ter uma classe que possui um método que recebe uma string e valida se é um número e se o mesmo não possui mais de 10 caracteres.
Os comandos para criar o projecto da class library.
dotnet new classlib -o xUnitCoverletLib
cd xUnitCoverletLib
dotnet new gitignore
cd ..
using System.Text.RegularExpressions;
namespace xUnitCoverletLib;
public class Numbers
{
public static bool IsValid(String number)
{
if (!IsNumber(number))
{
return false;
}
if (number.Trim().Length > 10)
{
return false;
}
return true;
}
public static bool IsNumber(string number)
{
Regex regex = new(@"^[0-9]+$", RegexOptions.None, TimeSpan.FromMilliseconds(5));
return regex.IsMatch(number);
}
}
Teste com xUnit
A seguir temos o projecto de teste que usa o xUnit, ao ser criado, já inclui a biblioteca coverlet. Para confirmar isso, depois de criar o projecto de teste, se abrirmos o ficheiro xUnitCoverletTest.csproj, teremos como uma das PackageReference o coverlet.collector.
Os comandos para criar o projecto de teste.
dotnet new xunit -o xUnitCoverletTest
cd xUnitCoverletTest
dotnet new gitignore
cd ..
A classe de teste valida o resultado da execução do método. No nosso exemplo se o método receber uma string com apenas números e que não tenha mais de 10 caracteres é suposto devolver tue, caso contrário o resultado é false.
Foi usado duas abordagens para testes, uma que permite passar vários inputs como parâmetro para testar ([Theory]) e outro sem parâmetros ([Fact]).
using xUnitCoverletLib;
namespace xUnitCoverletTest;
public class NumbersTests
{
[Fact]
public void ValidOneNumber()
{
// ARRANGE
string number = "123324324";
// ACT
bool isNumer = Numbers.IsValid(number);
// ASSERT
Assert.True(isNumer);
}
public static readonly object[][] InsertValidNumbers = {
new object[] { "123456782"},
new object[] { "1"},
new object[] { "233"},
};
[Theory, MemberData(nameof(InsertValidNumbers))]
public void ValidMultipleNumber(string number)
{
// ACT
bool isNumer = Numbers.IsValid(number);
// ASSERT
Assert.True(isNumer);
}
public static readonly object[][] InsertInvalidNumbers = {
new object[] { "12345678912"},
new object[] { "Ola"},
new object[] { "So Sabi"},
new object[] { "12345q6789"},
};
[Theory, MemberData(nameof(InsertInvalidNumbers))]
public void InvalidNumbers(string number)
{
// ACT
bool isNumer = Numbers.IsValid(number);
// ASSERT
Assert.False(isNumer);
}
}
O projecto de teste deve referenciar a nossa class library, para assim ser possível testar os métodos disponíveis na class library. O comando que se segue permite referência a class library. Para confirmar se ficou correto, pode-se abrir o ficheiro xUnitCoverletTest.csproj, conferir se existe uma tag de nome ProjectReference.
cd xUnitCoverletTest
dotnet add reference ../xUnitCoverletLib
Com os dois projectos criados, podemos criar uma soluction e adicionar os ambos na soluction , para efeitos de organização, segue as instruções abaixo e uma imagem de como deve passar a ser reconhecido pelo Visual Studio.
dotnet new sln --name xUnitCoverlet
dotnet sln add xUnitCoverletLib
dotnet sln add xUnitCoverletTest
Agora estamos em condições de testar o nossa class library, para isso segue o comando.
cd xUnitCoverletTest
dotnet test
O resultado foi oito testes bem sucedidos, zero testes sem sucesso. Pode estar a indagar como assim, mas só temos tres métodos, não parece fazer sentido, isso acontece porque, cada linha do array passado como parâmetro nos métodos com anotações [Theory] conta como um teste.
Cobertura de teste com Coverlet
Feito os testes, podemos medir o nível de cobertura de testes do nosso exemplo. Para coletar dados referente a cobertura com coverlet temos de adicionar ao comando de teste o parâmetro a seguir.
dotnet test --collect:"XPlat Code Coverage"
O resultado da execução do comando cria um ficheiro de nome coverage.cobertura.xml, que pode ser encontrado numa pasta cujo nome é um UUID, no nosso exemplo foi gerado o nome fe0cc9d8–90b1–4e59–9cdd-032d8fb423a8, a cada execução do comando é gerado uma pasta nova. Todas as pastas geradas se encontram dentro da pasta TestResults que pode ser encontrada na raiz do projeto de teste.
A interpretação visual do ficheiro do coverage.cobertura.xml não parece ser algo prático. Para melhor visualizar os dados coletados temos o projecto ReportGenerator permite a conversão do ficheiro gerado no caso coverage.cobertura.xml, num relatório de fácil leitura e compreensão. A instalação do ReportGenerator pode ser feita seguindo as instruções abaixo.
dotnet tool install -g dotnet-reportgenerator-globaltool
A ferramenta permite gerar para vários formatos. O comando a seguir gerar relatórios no formato html.
Nota : O valor “fe0cc9d8–90b1–4e59–9cdd-032d8fb423a8” na linha que se segue, deve ser alterado de acordo com a pasta gerada ou que se quer gerar o relatório.
reportgenerator "-reports:TestResults\fe0cc9d8-90b1-4e59-9cdd-032d8fb423a8\coverage.cobertura.xml" "-targetdir:coveragereport" -reporttypes:Html
Neste exemplo tivemos 100% de cobertura de teste, temos uma vista agregada e um vista que apresenta as classes testadas, conforme indicam as imagens a seguir.
O projecto pode ser encontrado no repositório.
Conclusão
Qualquer alteração feita na class library que altere os resultados esperado não passa nos testes, desta forma é assegurada a coerência do comportamento do software por exemplo no processo de manutenção.
Nem sempre temos a proeza de ter uma cobertura a 100%, neste caso a ferramenta indica para cada classe no nível de cobertura e conseguimos ainda perceber quais métodos não estão cobertos. A cobertura a 100% num projecto real pode ser uma utopia, deve-se estabelecer o que é crítico e que deve ter um determinado nível de cobertura.