Pruebas de mutación en TypeScript: 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

¿Está seguro de que sus tests realmente funcionan?

\n\n

Imagine: tiene un 95% de cobertura de código, todos los tests están en verde, el CI/CD pasa sin errores. El equipo está contento, el jefe tranquilo. Pero un día aparece un error en producción. Abre el código, mira los tests y se da cuenta: simplemente verifican que la función devuelve un número, pero no miran su valor. O peor aún: el test está escrito de tal manera que pasa en cualquier escenario.

\n\n

¿Le suena familiar? Bienvenido al mundo donde el porcentaje de cobertura es solo un número, no una garantía de calidad. Aquí entra en escena la prueba de mutación (mutation testing): una metodología que realmente hace sufrir a sus tests y los obliga a demostrar su validez.

\n\n

En este artículo, analizaremos cómo implementar mutation testing en un proyecto TypeScript usando Stryker, y por qué podría convertirse en su nueva herramienta favorita (o en una nueva fuente de dolores de cabeza).

\n\n

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

\n\n

Las pruebas de mutación consisten en tomar su código, introducir deliberadamente pequeños errores (mutaciones) y ver si sus tests fallan. Si los tests no fallan, significa que está probando incorrectamente. Si fallan, bien hecho, pero el mutante ha sido eliminado.

\n\n

¿Suena a sadismo? Quizás. Pero funciona. Imagine que es un ingeniero de QA que se ha colado en el código y ha cambiado === por ==, > por < o ha eliminado una llamada a una función. Un buen test debería detectarlo. Uno malo lo dejará pasar.

\n\n

En TypeScript, con su tipado estricto, las pruebas de mutación son especialmente útiles: detectan casos donde los tipos son correctos, pero la lógica no.

\n\n

¿Por qué la cobertura de código (code coverage) normal es una ilusión?

\n\n

Supongamos que tiene una función:

\n\n
function calculateDiscount(price: number, isPremium: boolean): number {\n  if (isPremium) {\n    return price * 0.8;\n  }\n  return price;\n}
\n\n

Y un test:

\n\n
test('returns number', () => {\n  const result = calculateDiscount(100, true);\n  expect(typeof result).toBe('number');\n});
\n\n

¿Cobertura? 100%. ¿El test? Pasa. ¿Y si cambiamos price * 0.8 por price * 0.5? El test seguirá pasando: solo verifica el tipo, no el valor. He aquí una falsa sensación de seguridad.

\n\n

Las pruebas de mutación resaltarán esa mutación y dirán: «Oye, tu test no notó que cambié el descuento». Y tendrá que escribir una verificación adecuada.

\n\n

Cómo funciona el mutation testing en TypeScript: paso a paso

\n\n

Todo el proceso se puede dividir en tres etapas:

\n\n
    \n
  1. Generación de mutantes — la herramienta copia su código y realiza un cambio a la vez. Por ejemplo, cambia && por || o elimina una llamada a un método.
  2. \n
  3. Ejecución de tests — para cada mutante se ejecutan sus tests.
  4. \n
  5. Análisis de resultados — si los tests fallan, el mutante está «muerto». Si no, «sobrevive», y esa es una zona de riesgo.
  6. \n
\n\n

La métrica principal es el mutation score (porcentaje de mutantes muertos). El 100% es el ideal, pero en la práctica un 80-90% ya es excelente.

\n\n

Herramienta nº1 para TypeScript: Stryker

\n\n

En el mundo de TypeScript y JavaScript, el rey de las pruebas de mutación es Stryker (StrykerJS). Es rápido, funciona con Jest, Vitest, Mocha e incluso con tests de navegador. Se configura en 5 minutos.

\n\n

Instalación:

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

Creamos el archivo de configuración stryker.conf.js:

\n\n
// stryker.conf.js\nexport default {\n  mutate: ['src/**/*.ts', '!src/**/*.spec.ts'], // qué mutar\n  testRunner: 'jest',\n  reporters: ['html', 'clear-text', 'progress'],\n  jest: {\n    projectType: 'custom',\n    configFile: 'jest.config.ts',\n  },\n  concurrency: 4, // cuántos mutantes                

Blogs

Book Recommendations