Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Las pruebas de mutación son una manera de evaluar la calidad de nuestras pruebas unitarias. Para las pruebas de mutación, la herramienta Stryker.NET realiza automáticamente mutaciones en el código, ejecuta pruebas y genera un informe detallado con los resultados.
Escenario de prueba de ejemplo
Considere un ejemplo PriceCalculator.cs clase con un Calculate método que calcule el precio, teniendo en cuenta el descuento.
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);
}
}
El método anterior está cubierto por las siguientes pruebas unitarias:
[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));
}
El código anterior resalta dos proyectos, uno para el servicio que actúa como PriceCalculator y el otro es el proyecto de prueba.
Instalación de la herramienta global
En primer lugar, instale Stryker.NET. Para ello, debe ejecutar el comando :
dotnet tool install -g dotnet-stryker
Para ejecutar stryker, invoque desde la línea de comandos del directorio donde se encuentra el proyecto de prueba unitaria:
dotnet stryker
Una vez ejecutadas las pruebas, se muestra un informe en la consola.
Stryker.NET guarda un informe HTML detallado en el directorio StrykerOutput.
Ahora, considere lo que son los mutantes y lo que significan "sobrevivió" y "mató". Un mutante es un pequeño cambio en el código que Stryker realiza a propósito. La idea es sencilla: si las pruebas son buenas, deben detectar el cambio y fallar. Si siguen superando, es posible que las pruebas no sean lo suficientemente fuertes.
En nuestro ejemplo, un mutante será el reemplazo de la expresión price <= 0, por ejemplo, con price < 0, después de la cual se ejecutan pruebas unitarias.
Stryker admite varios tipos de mutaciones:
| Tipo | Descripción |
|---|---|
| Equivalente | El operador equivalente se usa para reemplazar un operador por su equivalente. Por ejemplo, x < y se convierte en x <= y. |
| Aritméticos | El operador aritmético se usa para reemplazar un operador aritmético por su equivalente. Por ejemplo, x + y se convierte en x - y. |
| Cuerda | El operador de cadena se usa para reemplazar una cadena por su equivalente. Por ejemplo, "text" se convierte en "". |
| Lógico | El operador lógico se usa para reemplazar un operador lógico por su equivalente. Por ejemplo, x && y se convierte en x \|\| y. |
Para obtener más tipos de mutación, consulte la documentación de Stryker.NET: Mutaciones .
Interpretación de los resultados de las pruebas de mutación
Después de ejecutar Stryker.NET, recibirá un informe que clasifica a los mutantes como eliminados, sobrevivientes o timeout. Aquí se muestra cómo interpretar y actuar sobre estos resultados:
- Eliminado: estos son los cambios que detectaron correctamente las pruebas. Un gran número de mutantes muertos indica que el conjunto de pruebas detecta eficazmente errores lógicos.
- Sobrevivió: estos cambios no fueron detectados por pruebas. Revise los para identificar brechas en la cobertura de pruebas o las aserciones que son demasiado débiles. Concéntrese en agregar pruebas unitarias dirigidas que fallarían si el mutante fuera real.
- Tiempo de espera: estas mutaciones han provocado que el código se bloquee o supere el tiempo permitido. Esto puede ocurrir con bucles infinitos o rutas de acceso no optimizadas. Investigue la lógica de código o aumente el umbral de tiempo de espera si es necesario.
Nota:
No persiga una puntuación de mutación de 100%. En su lugar, céntrese en áreas de alto riesgo o críticas para la empresa en las que los errores no detectados serían más costosos.
Adición de pruebas de mutación al flujo de trabajo de CI/CD
Puede integrar sin problemas las pruebas de mutación en los flujos de trabajo de integración y entrega continuos. Por ejemplo, Stryker.NET se pueden configurar para que se ejecuten en la configuración de Azure Pipelines o Acciones de GitHub, lo que le permite aplicar umbrales de calidad como parte del proceso de prueba automatizado.
Personalización
Además de establecer umbrales para la canalización, Stryker.NET ofrece la posibilidad de tener configuraciones diferentes para cada una de las necesidades del proyecto. Puede personalizar el comportamiento mediante el archivo stryker-config.json .
{
"stryker-config": {
"ignore-mutations": [
"string",
"logical"
],
"ignore-methods": [
"*Logs"
],
"mutate": [
"!**/Migrations/*",
"!**/*.Designer.cs"
]
}
}
-
ignore-mutations: los tipos de mutación que se excluirán de las pruebas porque son ruidosos o no son relevantes para la lógica de la aplicación. Aparecerán en los informes como
Ignored. -
ignore-methods: Esto le permite omitir métodos completos en función de sus signaturas. También aparecen en los informes como
Ignored. En el ejemplo anterior, se omiten todos los métodos que terminan en "Registros". - mutar: sin esta opción, Stryker intentará mutar todos los archivos del proyecto. Con esto, puede omitir archivos o carpetas completas. En el ejemplo anterior, todo lo que hay dentro de una carpeta Migrations y todos . Designer.cs archivos (que normalmente se generan automáticamente) se omiten.
Para obtener más información, consulte Stryker: Configuration.
Mejora incremental
Si, después de cambiar el código, las pruebas unitarias se pasan correctamente, no son suficientemente robustas y el mutante sobrevive. Después de las pruebas de mutación, cinco mutantes sobreviven.
Vamos a agregar datos de prueba para los valores de límite y volver a ejecutar pruebas de mutación.
[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 puede ver, después de corregir los mutantes equivalentes, solo tenemos mutaciones de cadena restantes, que podemos 'matar' fácilmente comprobando el texto del mensaje de excepción.
[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);
}
Las pruebas de mutación ayudan a encontrar oportunidades para mejorar las pruebas que las hacen más confiables. Te obliga a comprobar no solo la "ruta feliz", sino también casos límite complejos, lo que disminuye la probabilidad de errores en la producción.