Pruebas de Mutación en Rust: cómo encontrar lo que incluso el 100% de las pruebas pueden pasar por alto

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

La paradoja de la cobertura del 100% — o por qué sus pruebas podrían estar mintiendo

\n\n

Escribió código perfecto en Rust. El compilador está contento, clippy calla, las pruebas pasan en verde, y el informe de cobertura proclama con orgullo: el 100% de las líneas están cubiertas. ¿Puede relajarse? No. Las pruebas de mutación en Rust son precisamente la técnica que le mostrará que sus pruebas, posiblemente, están protegiendo el aire, no la lógica.

\n\n

Imagine: accidentalmente cambia + por - en una función crítica. ¿Las pruebas siguen en verde? Felicidades, ha encontrado un mutante — un defecto que vive en su código, pero que las pruebas no detectan. En este artículo, analizaremos cómo las pruebas mutacionales en Rust ayudan a encontrar estos puntos ciegos, y lo implementaremos en un proyecto real.

\n\n

¿Qué son las pruebas de mutación (y por qué Rust es ideal para ellas)?

\n\n

Las pruebas de mutación (o pruebas mutacionales) es un método para evaluar la calidad de las pruebas, en el que se introducen pequeños cambios artificiales (mutaciones) en el código fuente, y luego se ejecutan las pruebas. Si las pruebas no fallan con el código mutado, significa que el mutante ha sobrevivido. Cuantos más mutantes sobrevivan, peores son sus pruebas.

\n\n

Rust es un lenguaje que por sí mismo detecta clases enteras de errores en la etapa de compilación: condiciones de carrera, punteros nulos, use-after-free. Pero las pruebas de mutación en Rust se centran precisamente en los errores lógicos — lo que el compilador no ve. Y esto hace de Rust una plataforma ideal: ya está protegido contra el 70% de los errores típicos, y las pruebas mutacionales eliminan el 30% restante.

\n\n

¿Cómo se ve una mutación en el código Rust?

\n\n

Las mutaciones típicas incluyen:

\n
    \n
  • Cambio de operadores: a + b → a - b
  • \n
  • Inversión de condiciones: if x > 5 → if x < 5
  • \n
  • Reemplazo de constantes: let n = 42 → let n = 0
  • \n
  • Eliminación de llamadas a funciones: process(data) → llamada vacía
  • \n
  • Cambio de límites de iteración: 0..n → 0..=n
  • \n
\n\n

Si después de tal mutación al menos una prueba no falla — significa que perdió el tiempo escribiendo esa prueba. No está verificando lo que debería.

\n\n

Herramientas para pruebas de mutación en Rust: cargo-mutants

\n\n

Hoy en día, la herramienta principal para las pruebas mutacionales en Rust es cargo-mutants. Es madura, se mantiene activamente y proporciona informes legibles. La instalación es sencilla:

\n\n
cargo install cargo-mutants\n
\n\n

Después de eso, simplemente ejecute:

\n\n
cargo mutants\n
\n\n

Y la herramienta encontrará todas las mutaciones en su código, ejecutará las pruebas y generará un informe HTML con indicación de colores: verde — mutantes eliminados (las pruebas funcionaron), rojo — sobrevivientes (problema).

\n\n

Alternativas en el radar

\n
    \n
  • mutagen — más antiguo, pero aún funcional. Menos mutaciones, más rápido en proyectos pequeños.
  • \n
  • cargo-insta — no es exactamente pruebas de mutación, pero está cerca: ayuda a verificar visualmente los cambios en las instantáneas de prueba.
  • \n
\n\n

Para trabajo serio, recomiendo cargo-mutants — ofrece el mejor equilibrio entre velocidad y profundidad de análisis.

\n\n

Ejemplo práctico: probando una calculadora de descuentos

\n\n

Supongamos que tenemos un módulo para calcular descuentos en una tienda en línea en Rust:

\n\n
// src/discount.rs\n\npub fn calculate_discount(price: f64, customer_age: u32) -> f64 {\n    let mut discount = 0.0;\n\n    // 10% de descuento para jubilados (mayores de 65)\n    if customer_age > 65 {\n        discount += 0.1;\n    }\n\n    // 5% de descuento adicional por compras superiores a 1000 rublos\n    if price > 1000.0 {\n        discount += 0.05;\n    }\n\n    // Descuento máximo no superior al 30%\n    if discount > 0.30 {\n        discount = 0.30;\n                

Blogs

Book Recommendations