Mutation Testing en PHP: Cómo escribir tests que detectan errores reales

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

Introducción: ¿Por qué el 100% de cobertura de código es una ilusión?

\n

¿Alguna vez te has sentido orgulloso de un informe de PHPUnit que dice «Code Coverage: 98%»? Y luego lanzas una versión y en producción aparece un error crítico. ¿Te suena? El problema es que la cobertura clásica (line/statement coverage) solo mide qué líneas de código se ejecutaron. No verifica si tus tests realmente prueban la lógica.

\n

Imagina que escribes un test que verifica que una función devuelve true. Pero la función siempre devuelve true, incluso si la rompes. El test pasará, la cobertura será del 100%, pero no detectarás el error. Aquí es donde entra el mutation testing en PHP: una técnica que cambia la forma tradicional de pensar sobre las pruebas.

\n

En este artículo, veremos qué son las pruebas de mutación, cómo funcionan con código PHP real y cómo implementarlas en tu proyecto usando la librería Infection.

\n\n

¿Qué es Mutation Testing? (Y por qué no es magia)

\n

Mutation testing (o pruebas de mutación) es un método para evaluar la calidad de los tests. Consiste en introducir pequeños cambios intencionales (mutaciones) en el código fuente. Si después del cambio el test falla, el mutante ha muerto y el test funciona. Si el test sigue pasando, el mutante ha sobrevivido, lo que significa que tu test no está verificando nada.

\n

Una analogía simple: tú eres un guardia de seguridad y tus tests son las cámaras. El mutation testing introduce «intrusos» (mutantes) en el edificio. Si la cámara (el test) detecta al intruso, funciona. Si el intruso pasea tranquilamente, la cámara es inútil.

\n

En PHP, la herramienta principal para esto es Infection, una librería que ejecuta PHPUnit en cada mutante y analiza los resultados.

\n\n

¿Cómo se ve en la práctica?

\n

Supongamos que tienes una clase simple de calculadora:

\n
<?php\nclass Calculator\n{\n    public function add(int $a, int $b): int\n    {\n        return $a + $b;\n    }\n}
\n

Y su test correspondiente:

\n
<?php\nuse PHPUnit\\Framework\\TestCase;\n\nclass CalculatorTest extends TestCase\n{\n    public function testAdd()\n    {\n        $calc = new Calculator();\n        $result = $calc->add(2, 3);\n        $this->assertEquals(5, $result);\n    }\n}
\n

La herramienta Infection creará un mutante, reemplazando + por -:

\n
return $a - $b;
\n

Luego ejecutará el test. Si el test falla, el mutante ha muerto. Si el test pasa (y pasará, porque solo verificas 2+3, no 2-3), el mutante ha sobrevivido. Esto es una señal: tu test no es lo suficientemente bueno.

\n\n

Instalación de Infection y primer uso

\n

Para empezar, instala Infection a través de Composer:

\n
composer require --dev infection/infection
\n

Luego, crea un archivo de configuración infection.json (o ejecuta vendor/bin/infection para una configuración interactiva).

\n

Ejemplo de configuración mínima:

\n
{\n    "source": {\n        "directories": [\n            "src"\n        ]\n    },\n    "phpUnit": {\n        "configDir": "."\n    },\n    "mutators": {\n        "@default": true\n    }\n}
\n

Ejecuta las pruebas de mutación:

\n
vendor/bin/infection --min-msi=100
\n

Donde --min-msi=100 indica que queremos que el 100% de los mutantes mueran (un objetivo ideal, pero raramente alcanzable). Infection generará un informe en formato HTML (carpeta infection.html), donde los mutantes supervivientes se marcarán en rojo.

\n\n

Tipos de mutaciones en PHP (con ejemplos)

\n

Infection admite decenas de mutadores. Estos son los más comunes con los que te encontrarás:

\n\n

1. Operadores matemáticos

\n

Reemplazo de + por -, * por /, % por *, etc.

\n
// Código original\nreturn $price * $quantity;\n\n// Mutante\nreturn $price / $quantity;
\n

Blogs

Book Recommendations