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 .
Interpretar os resultados do teste de mutação
Depois de executar Stryker.NET, você receberá um relatório que categoriza mutantes como mortos, sobrevividos ou tempo limite. Veja como interpretar e agir sobre estes resultados:
- Eliminadas: Estas são as alterações que seus testes identificaram com êxito. Um alto número de mutantes mortos indica que seu conjunto de testes detecta efetivamente erros lógicos.
- Sobreviveu: Essas alterações não foram detectadas pelos seus testes. Examine-os para identificar lacunas na cobertura de teste ou declarações que são muito fracas. Concentre-se em adicionar testes de unidade direcionados que falhariam se o mutante fosse real.
- Tempo limite: essas mutações fizeram com que seu código travasse ou excedesse o tempo permitido. Isso pode acontecer com loops infinitos ou caminhos não otimizados. Investigue a lógica de código ou aumente o limite de tempo limite, se necessário.
Observação
Não busque uma pontuação de mutação de 100%. Em vez disso, concentre-se em áreas críticas de alto risco ou de negócios em que bugs não detectados seriam mais caros.
Adicionando testes de mutação ao fluxo de trabalho de CI/CD
Você pode integrar perfeitamente o teste de mutação aos fluxos de trabalho de integração e entrega contínuos. Por exemplo, Stryker.NET pode ser configurado para ser executado na configuração do Azure Pipelines ou do GitHub Actions, permitindo que você imponha limites de qualidade como parte do processo de teste automatizado.
Personalização
Além de definir limites para o pipeline, Stryker.NET oferece a possibilidade de ter configurações diferentes para cada uma das suas necessidades de projeto. Você pode personalizar o comportamento usando o arquivo stryker-config.json .
{
"stryker-config": {
"ignore-mutations": [
"string",
"logical"
],
"ignore-methods": [
"*Logs"
],
"mutate": [
"!**/Migrations/*",
"!**/*.Designer.cs"
]
}
}
-
ignore-mutações: tipos de mutação a serem excluídos do teste porque são ruidosos ou não são relevantes para a lógica da aplicação. Eles aparecerão em seus relatórios como
Ignored. -
ignore-methods: você pode usá-lo para ignorar métodos inteiros com base em suas assinaturas. Elas também aparecem em seus relatórios como
Ignored. No exemplo anterior, todos os métodos que terminam em "Logs" são ignorados. - mutar: sem essa opção, o Stryker tentará alterar todos os arquivos em seu projeto. Com isso, você pode ignorar arquivos ou pastas inteiras. No exemplo anterior, tudo dentro de uma pasta Migrações, bem como todos os arquivos .Designer.cs (que geralmente são gerados automaticamente), são ignorados.
Para obter mais informações, consulte Stryker: Configuração.
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.