Pruebas de Mutación en JavaScript: cómo atrapar errores esquivos

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? Hora de las pruebas de mutación

\n\n

¿Escribes pruebas unitarias, la cobertura de código (code coverage) es agradable a la vista — 85%, 90%, y aún así aparecen errores en producción? Un dolor familiar. La métrica clásica de cobertura (line/branch coverage) es, básicamente, una medida de qué líneas de código se ejecutaron. Pero no dice nada sobre qué tan bien las pruebas verifican la lógica. Pueden simplemente "recorrer" las líneas sin verificar la corrección del resultado.

\n\n

Aquí es donde entran en escena las pruebas de mutación (mutation testing). Es como una inyección de vacuna para tu código: introducimos deliberadamente pequeños "virus" (mutaciones) en el programa y vemos si nuestras pruebas los eliminan. Si la prueba no falla después de cambiar === por !== — tus pruebas son débiles. En este artículo en pythonlib.ru, analizaremos cómo implementar pruebas de mutación en un proyecto de JavaScript para hacer que tu código sea verdaderamente robusto.

\n\n

¿Qué son las pruebas de mutación en palabras simples?

\n\n

Imagina que eres un ingeniero de QA que cambia secretamente una línea en el código del desarrollador:

\n\n
// Era\nif (user.age >= 18) { allowAccess(); }\n\n// Se convirtió (mutación)\nif (user.age > 18) { allowAccess(); }
\n\n

Si después de este cambio las pruebas unitarias siguen siendo verdes — las pruebas no verifican la condición límite 18. El framework de mutación realiza miles de estos cambios automáticamente y genera un informe: cuántas mutaciones sobrevivieron (survived). Cuantos menos mutantes sobrevivan, más fiables son tus pruebas.

\n\n

Tipos principales de mutaciones en JavaScript:

\n
    \n
  • Cambio de operadores de comparación: > → >=, === → !==
  • \n
  • Eliminación de llamadas a funciones: validate() → cadena vacía
  • \n
  • Cambio de literales numéricos: 0 → 1, 100 → 0
  • \n
  • Inversión de condiciones lógicas: if (isActive) → if (!isActive)
  • \n
  • Sustitución de valores de cadena (menos común, pero sucede)
  • \n
\n\n

Por qué la cobertura de código es una falsa seguridad

\n\n

Muchos desarrolladores de JavaScript se dejan engañar por una cobertura de código del 100%. Veamos un ejemplo real:

\n\n
// Función que determina un descuento\nfunction getDiscount(price, isPremium) {\n    if (price > 100 && isPremium) {\n        return price * 0.8;\n    }\n    return price;\n}
\n\n

Y la prueba "ideal":

\n\n
test('debería devolver el precio para no premium', () => {\n    expect(getDiscount(50, false)).toBe(50);\n});\n\ntest('debería devolver el precio para premium con precio bajo', () => {\n    expect(getDiscount(50, true)).toBe(50);\n});
\n\n

Cobertura: 100% de líneas, 100% de ramas. ¡Pero la mutación price > 100 → price >= 100 sobrevivirá! Las pruebas no verifican el valor límite 100.01 o exactamente 100. Las pruebas de mutación resaltarán instantáneamente este "punto ciego".

\n\n

Herramienta nº1: Stryker Mutator para JavaScript

\n\n

La herramienta más popular y madura para pruebas de mutación en el mundo de JS es Stryker Mutator (o simplemente Stryker). Es compatible con Jest, Mocha, Jasmine, Karma y funciona con TypeScript.

\n\n

Instalación e inicio rápido

\n\n

Instala Stryker globalmente o localmente en tu proyecto:

\n\n
npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
\n\n

Crea un archivo de configuración stryker.conf.json:

\n\n
{\n  "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",\n  "packageManager": "npm",\n  "reporters": ["html", "clear-text", "progress"],\n  "testRunner": "jest",\n  "coverageAnalysis": "perTest",\n  "thresholds": {\n    "high": 80,\n    "low": 60,\n    "break": 50\n  }\n}
\n\n

Ejecuta la mutación

Blogs

Book Recommendations