Pruebas de mutación en Java: rompemos el código para mejorarlo

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

Tus pruebas mienten, y no lo sabes

\n\n

Imagina la situación: escribiste pruebas unitarias, el code coverage (cobertura de código) es del 95%. El jefe está contento, el pipeline de CI/CD está en verde. Pero el viernes por la noche llega un bug a producción. ¿Te suena?

\n\n

El problema es que la cobertura de código clásica es una ilusión. Muestra qué líneas de código se ejecutaron, pero no responde a la pregunta principal: ¿realmente las pruebas verifican la lógica? Aquí es donde entra en escena el mutation testing — una metodología que "rompe" sin piedad tu código para comprobar qué tan buenas son tus pruebas.

\n\n

En este artículo, analizaremos qué son las pruebas de mutación en Java, cómo funcionan y mostraremos ejemplos reales usando la herramienta PIT (Pitest). Aprenderás cómo convertir tus pruebas de "una marca en el informe" en una protección real contra bugs.

\n\n

¿Qué es el mutation testing y por qué es importante?

\n\n

El mutation testing (o pruebas de mutación) es un método para evaluar la calidad de las pruebas, en el que se introducen pequeños cambios deliberadamente incorrectos en el código fuente (llamados mutaciones). Luego se ejecutan tus pruebas. Si al menos una prueba falla, el mutante está "muerto", la prueba funcionó. Si las pruebas pasan, el mutante "sobrevive", y eso es una señal de alerta.

\n\n

Una metáfora simple: verificas qué tan bien tu alarma atrapa a los ladrones. Colocas un "ladrón" (mutante) y observas si la alarma (prueba) se activa. Si el ladrón se va tranquilamente, la alarma no sirve para nada.

\n\n

¿Por qué el code coverage es un mal indicador?

\n\n

Imagina este código:

\n\n
public boolean isAdult(int age) {\n    if (age >= 18) {\n        return true;\n    }\n    return false;\n}
\n\n

Y la prueba:

\n\n
@Test\nvoid testIsAdult() {\n    assertTrue(isAdult(18));\n}
\n\n

La cobertura de código mostrará el 100% (¿se ejecutaron ambas ramas del if-else?). ¡No! Solo se ejecutó la rama true. Pero la prueba está en verde. Ahora imagina que alguien cambia accidentalmente la condición a age > 21. La prueba seguirá pasando, porque 18 > 21 es false, y assertTrue espera true. El bug llegará a producción.

\n\n

El mutation testing detectará esta situación al instante, creando un mutante que cambia >= por > o invierte la condición.

\n\n

Cómo funciona el mutation testing: teoría simplificada

\n\n

El proceso consta de tres etapas:

\n\n
    \n
  1. Generación de mutantes. La herramienta (por ejemplo, PIT) crea múltiples versiones de tu código con pequeños cambios: modifica operadores, elimina llamadas a métodos, invierte condiciones.
  2. \n
  3. Ejecución de pruebas. Para cada mutante, se ejecutan tus pruebas unitarias.
  4. \n
  5. Análisis de resultados. Si la prueba falla, el mutante está muerto. Si pasa, está vivo. El porcentaje de mutantes muertos se llama mutation score (puntuación de mutaciones).
  6. \n
\n\n

Un buen indicador: 80-90% de mutantes muertos. Todo lo que esté por debajo es motivo para reescribir las pruebas.

\n\n

Ejemplo práctico en Java con PIT

\n\n

Tomemos un ejemplo real. Supongamos que tenemos un servicio para calcular descuentos:

\n\n
public class DiscountService {\n    public double calculateDiscount(double amount, boolean isPremium) {\n        double discount = 0.0;\n\n        if (amount > 100) {\n            discount = 0.1; // 10% de descuento\n        }\n\n        if (isPremium) {\n            discount += 0.05; // 5% adicional\n        }\n\n        // El descuento no puede ser mayor al 30%\n        if (discount > 0.3) {\n            discount = 0.3;\n        }\n\n        return discount;\n    }\n}
\n\n

Escribamos una prueba ingenua:

\n\n
import org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass DiscountServiceTest {\n\n    private final DiscountService service = new                

Blogs

Book Recommendations