Pruebas de mutación, explicadas
Spec: ISO/IEC/IEEE 29119-4 ISO/IEC/IEEE 29119-4 Spec: PHPUnit PHPUnit Evidence: Test-backed
Panorama general
Sección titulada «Panorama general»La cobertura de líneas indica que una línea se ejecutó durante el conjunto de pruebas. No indica que alguna prueba habría fallado si esa línea fuera incorrecta. Las pruebas de mutación cierran esa brecha: rompen el código de forma deliberada y comprueban si las pruebas lo detectan. Esta página explica qué significa una puntuación de mutación y cómo NextPDF la usa como diagnóstico, no como trofeo.
Por qué esto importa
Sección titulada «Por qué esto importa»La cobertura es la métrica de pruebas en la que más se confía y una de las más engañosas. Una prueba que llama a un método y no afirma nada ejecuta todas sus líneas: cobertura perfecta, detección nula. Las normas son explícitas al respecto: la ordenación de los criterios de cobertura no indica su capacidad para exponer fallos. Esa capacidad es la propiedad que denominan eficacia de las pruebas (ISO/IEC/IEEE 29119-4, §C.2.4). Un porcentaje de cobertura y una garantía de detección de fallos son afirmaciones distintas.
Para un motor de PDF esto no es una cuestión académica. Una comprobación del intervalo de bytes de una firma, un desplazamiento de referencia cruzada, una rama de codificación: las pruebas pueden «cubrir» por completo todo esto sin afirmar nunca el valor que importa. Un conjunto de pruebas en verde sostenido por pruebas débiles es peor que una brecha honesta, porque desincentiva activamente que alguien lo revise.
La versión breve
Sección titulada «La versión breve»- Las pruebas de mutación realizan miles de cambios pequeños y deliberados (mutantes) en el código fuente: cambiar un
<por un<=, un+por un-, un valor dereturn, y vuelven a ejecutar las pruebas contra cada uno. - Si una prueba falla con un mutante, el mutante queda eliminado: alguna prueba afirmó de verdad ese comportamiento. Si todas las pruebas siguen pasando, el mutante escapó: el comportamiento se ejecutó, pero nunca se comprobó.
- El indicador de puntuación de mutación (MSI) es, a grandes rasgos, la proporción de mutantes eliminados respecto del total de mutantes no equivalentes. Mide si las pruebas detectan los cambios, no si ejecutan el código.
- Algunos mutantes son equivalentes: no pueden cambiar el comportamiento observable, por lo que ninguna prueba puede eliminarlos. Contarlos como fallos es deshonesto. NextPDF los demuestra y los registra en un libro de mutaciones, en lugar de descartarlos de manera informal.
- NextPDF usa el MSI para encontrar y reforzar pruebas débiles. Es una puerta de diagnóstico en la integración continua, no una cifra de marketing.
Cómo lo aborda NextPDF
Sección titulada «Cómo lo aborda NextPDF»La mutación se ejecuta en el motor con Infection. Se configura sobre el árbol de código fuente de producción, con las familias de mutadores aritméticos, booleanos, de límite condicional, de igualdad, de valor de retorno y de eliminación habilitadas: exactamente los operadores que exponen la lógica «ejecutada pero sin afirmar». El flujo es mecánico:
- Start green The suite must pass before mutation begins.
- Mutate Apply one small, deliberate change to the source.
- Re-run Run the tests that cover the mutated line.
- Killed A test failed — the behaviour is genuinely asserted.
- Escaped All tests still pass — a weak spot to strengthen.
- Equivalent No test can kill it because behaviour is unchanged — proven and ledgered, not scored as a miss.
Dos decisiones de diseño hacen que el número sea fiable. En primer lugar, la puntuación se usa como una puerta. La integración continua exige un MSI mínimo (y un MSI cubierto mínimo) y ejecuta una variante acotada al diff sobre las líneas modificadas. Como resultado, un cambio que añade código pero no afirmaciones reales se detecta en la revisión, no se descubre más tarde. En segundo lugar, NextPDF no descarta en silencio los mutantes incómodos. Los mutantes que son genuinamente semánticamente equivalentes (por ejemplo, !== frente a != cuando la tipificación estricta garantiza que ambos operandos comparten un tipo) se registran en un libro de mutaciones con una prueba explícita de demostración de equivalencia. Como resultado, el recuento de escapados refleja brechas reales, no un artificio contable. PHPStan Level 10, junto con strict_types y las propiedades tipadas, es lo que hace que esas demostraciones de equivalencia sean sólidas.
Lo que dice la evidencia
Sección titulada «Lo que dice la evidencia»Evidence: Test-backed Las pruebas de mutación se configuran en el motor sobre los directorios de código fuente de producción con las familias de mutadores que revelan comportamiento no afirmado habilitadas. Se aplican como una puerta de integración continua con un MSI mínimo y una variante acotada al diff. Es una comprobación de compilación, no algo secundario.
Evidence: Test-backed El problema de los mutantes equivalentes se gestiona con honestidad. Los mutantes semánticamente equivalentes se clasifican y se respaldan con pruebas dedicadas de demostración de equivalencia en un libro de mutaciones, y la solidez de cada demostración se apoya en PHPStan Level 10 junto con la tipificación estricta. El recuento de escapados representa, por tanto, comportamiento real no detectado, no ruido imposible de eliminar que se haya inflado para que la puntuación parezca peor.
Evidence: Standard-backed La mutación es una técnica reconocida, no un invento de NextPDF. Spec: ISO/IEC/IEEE 29119-4, §B.2.4 ISO/IEC/IEEE 29119-4 §B.2.4 describe la aplicación de mutaciones genéricas a los elementos de una especificación para derivar mutaciones específicas para las pruebas. La técnica es necesaria precisamente porque la misma norma indica que la relación de subsunción entre los criterios de cobertura no los ordena por su capacidad para exponer fallos (ISO/IEC/IEEE 29119-4, §C.2.4).
Evidence: Standard-backed La cobertura en sí está bien definida y limitada. Spec: PHPUnit PHPUnit distingue la cobertura de líneas, de ramas y de rutas. La cobertura de líneas solo registra que una línea ejecutable se ejecutó. Conocer la definición es lo que hace evidente su insuficiencia.
Ejemplo práctico
Sección titulada «Ejemplo práctico»Lo importante no es el comando: es lo que indica un mutante escapado:
<?php
declare(strict_types=1);
final class ByteRange{ // Suppose the production guard is: // if ($offset < 0) { throw new InvalidByteRange(); } public function assertNonNegative(int $offset): void { if ($offset < 0) { throw new InvalidByteRange('offset must be >= 0'); } }}
// A test that EXECUTES this line but does not assert the boundary:// $byteRange->assertNonNegative(5); // no exception expected, none asserted// gives 100% line coverage of assertNonNegative().//// Mutation flips `< 0` to `<= 0`. Behaviour now differs ONLY at $offset === 0.// If no test passes 0 and asserts what happens, every test still passes:// the mutant ESCAPED. Coverage said "tested"; mutation said "the boundary// is unasserted". The fix is a test that pins offset === 0, not a higher// target.//// composer mutation:diff → mutate only changed lines, enforce min MSI// composer mutation:full → full-tree mutation gateEse mutante escapado resume toda la propuesta de valor. Localizó una afirmación real, concreta y ausente que un informe de cobertura daba por totalmente probada.
Concepto erróneo habitual
Sección titulada «Concepto erróneo habitual»El concepto erróneo principal es que la puntuación de mutación es una nota que maximizar. Un MSI muy alto conseguido escribiendo pruebas para eliminar mutantes es tan vacío como una cobertura alta conseguida llamando a métodos sin afirmar nada. La métrica se ha manipulado y ya no mide la detección. NextPDF usa el MSI para encontrar pruebas débiles. El entregable es una afirmación mejor; el propósito, explícitamente, no es presumir.
El segundo concepto erróneo es que todo mutante superviviente es un defecto en las pruebas. Algunos mutantes son genuinamente equivalentes y no pueden ser eliminados por ninguna prueba, porque no cambian el comportamiento observable. Tratarlos como fallos produce una puntuación deshonesta y artificialmente baja, y hace que el equipo se acostumbre a ignorar el informe. La respuesta de NextPDF es demostrar la equivalencia de forma explícita y registrarla en el libro, no suprimirla en silencio ni fingir que el número es peor de lo que es.
Límites y fronteras
Sección titulada «Límites y fronteras»Las pruebas de mutación miden si las pruebas detectan los cambios inyectados. No demuestran que el código sea correcto. No miden el rendimiento ni la conformidad. No pueden eliminar un mutante verdaderamente equivalente. La puntuación de mutación actual, el umbral mínimo de MSI en vigor, el número de equivalentes registrados en el libro y cualquier cifra de cobertura son señales de calidad dinámicas, generadas a partir de los artefactos de integración continua y publicadas con la compilación. Están deliberadamente ausentes aquí, porque un número insertado en la prosa se queda obsoleto y se convierte en una pequeña mentira. El único hecho estable que esta página afirma es PHPStan Level 10, y esa es una propiedad de configuración que sustenta las demostraciones de equivalencia, no una medición.
La selección de mutadores, los umbrales y la política del libro son responsabilidad de la configuración de mutación del motor y pueden evolucionar. Esa configuración es la fuente de autoridad si alguna vez discrepa de esta página. Aquí no se hace ninguna afirmación sobre la eficacia de las pruebas de ninguna otra biblioteca.
Documentación relacionada
Sección titulada «Documentación relacionada»- La pirámide de pruebas de NextPDF: los cinco niveles cuya detección real de fallos auditan las pruebas de mutación.
- Tipos estrictos, en todas partes: cómo PHPStan Level 10 y la tipificación estricta hacen sólidas las demostraciones de mutantes equivalentes.
- Pruebas de archivo de referencia (golden-file): otro nivel cuya capacidad de detección ayudan a validar las pruebas de mutación.
Glosario
Sección titulada «Glosario»- Mutante: un único cambio pequeño y deliberado en el código fuente, usado para comprobar si el conjunto de pruebas detectaría ese cambio.
- Mutante eliminado: un mutante que hizo fallar al menos una prueba; el comportamiento se afirmó de verdad.
- Mutante escapado: un mutante con el que todas las pruebas siguieron pasando. El comportamiento se ejecutó pero nunca se afirmó: un punto débil que corregir.
- Mutante equivalente: un mutante que no puede cambiar el comportamiento observable, por lo que ninguna prueba puede eliminarlo. NextPDF los demuestra y los registra en el libro.
- MSI (indicador de puntuación de mutación): a grandes rasgos, los mutantes eliminados divididos entre el total de mutantes no equivalentes; una medida de detección, no de ejecución.
- Cobertura de líneas: una métrica que solo registra que una línea ejecutable se ejecutó durante el conjunto de pruebas; definida por PHPUnit e insuficiente por sí sola.