Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O teste de mutação é uma maneira de avaliar a qualidade de nossos testes de unidade. Para testes de mutação, a ferramenta Stryker.NET executa automaticamente mutações em seu código, executa testes e gera um relatório detalhado com os resultados.
Cenário de teste de exemplo
Considere um exemplo PriceCalculator.cs classe com um Calculate método que calcula o preço, levando em conta o desconto.
public class PriceCalculator
{
public decimal CalculatePrice(decimal price, decimal discountPercent)
{
if (price <= 0)
{
throw new ArgumentException("Price must be greater than zero.");
}
if (discountPercent < 0 || discountPercent > 100)
{
throw new ArgumentException("Discount percent must be between 0 and 100.");
}
var discount = price * (discountPercent / 100);
var discountedPrice = price - discount;
return Math.Round(discountedPrice, 2);
}
}
O método anterior é coberto pelos seguintes testes de unidade:
[Fact]
public void ApplyDiscountCorrectly()
{
decimal price = 100;
decimal discountPercent = 10;
var calculator = new PriceCalculator();
var result = calculator.CalculatePrice(price, discountPercent);
Assert.Equal(90.00m, result);
}
[Fact]
public void InvalidDiscountPercent_ShouldThrowException()
{
var calculator = new PriceCalculator();
Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, -1));
Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, 101));
}
[Fact]
public void InvalidPrice_ShouldThrowException()
{
var calculator = new PriceCalculator();
Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(-10, 10));
}
O código anterior destaca dois projetos: um para o serviço que atua como PriceCalculator e o outro é o projeto de teste.
Instalar a ferramenta global
Primeiro, instale Stryker.NET. Para fazer isso, você precisa executar o comando:
dotnet tool install -g dotnet-stryker
Para executar stryker, invoque-o da linha de comando no diretório em que o projeto de teste de unidade está localizado:
dotnet stryker
Após a execução dos testes, um relatório é exibido no console.
Stryker.NET salva um relatório HTML detalhado no diretório StrykerOutput.
Agora, considere o que são mutantes e o que significa "sobreviveu" e "morto". Um mutante é uma pequena mudança no código que Stryker faz de propósito. A ideia é simples: se seus testes forem bons, eles deverão detectar a alteração e falhar. ]Se ainda assim aprovarem, talvez seus testes não sejam suficientemente rigorosos.
Em nosso exemplo, um mutante será a substituição da expressão price <= 0, por exemplo, com price < 0, após a qual os testes de unidade são executados.
O Stryker dá suporte a vários tipos de mutações:
| Tipo | Descrição |
|---|---|
| Equivalente | O operador equivalente é usado para substituir um operador por seu equivalente. Por exemplo, x < y se tornará x <= y. |
| Aritmético | O operador aritmético é usado para substituir um operador aritmético por seu equivalente. Por exemplo, x + y se tornará x - y. |
| fio | O operador de cadeia de caracteres é usado para substituir uma cadeia de caracteres por seu equivalente. Por exemplo, "text" se tornará "". |
| Lógico | O operador lógico é usado para substituir um operador lógico por seu equivalente. Por exemplo, x && y se tornará x \|\| y. |
Para obter tipos de mutação adicionais, consulte a documentação Stryker.NET: Mutações .
Melhoria incremental
Se, depois de alterar o código, os testes de unidade forem aprovados com êxito, então eles não são suficientemente robustos e o mutante sobreviveu. Depois de testes de mutação, cinco mutantes sobrevivem.
Vamos adicionar dados de teste para valores de limite e executar o teste de mutação novamente.
[Fact]
public void InvalidPrice_ShouldThrowException()
{
var calculator = new PriceCalculator();
// changed price from -10 to 0
Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(0, 10));
}
[Fact] // Added test for 0 and 100 discount
public void NoExceptionForZeroAnd100Discount()
{
var calculator = new PriceCalculator();
var exceptionWhen0 = Record.Exception(() => calculator.CalculatePrice(100, 0));
var exceptionWhen100 = Record.Exception(() => calculator.CalculatePrice(100, 100));
Assert.Null(exceptionWhen0);
Assert.Null(exceptionWhen100);
}
Como você pode ver, depois de corrigir os mutantes equivalentes, temos apenas mutações de cadeia de caracteres restantes, que podemos facilmente 'matar' verificando o texto da mensagem de exceção.
[Fact]
public void InvalidDiscountPercent_ShouldThrowExceptionWithCorrectMessage()
{
var calculator = new PriceCalculator();
var ex1 = Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, -1));
Assert.Equal("Discount percent must be between 0 and 100.", ex1.Message);
var ex2 = Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, 101));
Assert.Equal("Discount percent must be between 0 and 100.", ex2.Message);
}
[Fact]
public void InvalidPrice_ShouldThrowExceptionWithCorrectMessage()
{
var calculator = new PriceCalculator();
var ex = Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(0, 10));
Assert.Equal("Price must be greater than zero.", ex.Message);
}
O teste de mutação ajuda a encontrar oportunidades para melhorar os testes que os tornam mais confiáveis. Isso força você a verificar não apenas o "caminho feliz", mas também casos de limite complexos, reduzindo a probabilidade de bugs na produção.