Durante años, en distintos foros, revisiones técnicas, comunidades internas y sesiones de arquitectura, he repetido siempre lo mismo:
el code coverage NO es una métrica fiable de calidad de tests.

Y aun así, periódicamente alguien vuelve a preguntar:

“¿Pero qué otra métrica tenemos para saber si los tests son buenos?”

Precisamente por eso escribo este artículo:
porque es hora de dejar claro —de una vez por todas— que necesitamos una métrica que mida efectividad real, no apariencia.

Esa métrica existe, y se llama:

Mutation Testing.

¿Qué es realmente “mutar” el código?

El Mutation Testing se basa en introducir fallos controlados en tu código de producción para comprobar si tus tests son capaces de detectarlos.

Estos fallos artificiales se llaman mutantes.

Una mutación es un cambio pequeño, pero semánticamente relevante, parecido a un error real que un desarrollador podría introducir:

  • cambiar == por !=

  • invertir un booleano (truefalse)

  • eliminar parte de una condición

  • modificar un valor de retorno

  • suprimir una llamada a un método

  • cambiar + por -

  • omitir un branch en un if

Es decir, se simulan errores típicos y frecuentes en el código.

Luego se ejecutan los tests:

✔ Si los tests fallan → el mutante queda “muerto” (buena señal).

✘ Si los tests pasan igual → el mutante “sobrevive” (mala señal).

Un mutante que sobrevive significa lo siguiente:

Tu test ejecuta esa parte del código, pero no verifica nada importante.

Es el equivalente a “he pasado por aquí, pero no he mirado nada”.

Por eso la cobertura engaña: ejecutar no es validar.

Caso real (datos ofuscados)

A continuación, un resultado obtenido con Stryker.NET, analizando un conjunto de controladores HTTP cuyos nombres han sido cambiados para no revelar ningún contexto real:

Resumen global

Métrica Valor
Score de Mutación 24.86%
Mutantes Killed 46
Mutantes Survived 122
Mutantes Sin Cobertura 17
Mutantes con Error de Compilación 20

Este proyecto tiene alrededor del 89% de cobertura.
Sin embargo, el mutation score solo llega al 24.86%.

Eso significa que 3 de cada 4 errores simulados NO son detectados por los tests.

Es decir, la suite de pruebas no está validando la lógica, solo está “pasando por encima”.

Resultado por componente (nombres neutralizados)

Archivo Score Killed Survived
XController.cs 75% 3 2
YController.cs 44.12% 15 26
ZController.cs 41.67% 5 13
AController.cs 36.36% 4 12
BController.cs 33.33% 4 13
CController.cs 15.22% 7 69
DController.cs 14.71% 5 42
EController.cs 11.11% 2 24
FController.cs 7.14% 1 21

Interpretación rápida:

  • XController: buena señal, los tests matan mutantes significativos.

  • Y–B: tests aceptables, pero siguen dejando pasar comportamientos erróneos.

  • C–F: tests frágiles o superficiales: la mayoría de mutantes sobreviven.

Este patrón se ve en prácticamente todos los proyectos que dependen únicamente de cobertura.

¿Qué revelan estos resultados?

✔ 1. La cobertura por líneas es un espejismo

Ejecutas código, sí, pero no necesariamente validas comportamiento.

✔ 2. Muchos tests solo validan casos “happy path”

Y obvian errores, excepciones, límites y flujos alternativos.

✔ 3. Los tests superficiales no aportan protección real

Pueden hacer que CI pase, pero no garantizan calidad.

✔ 4. El mutation score es objetivo

Ignora mocks vacíos, asserts triviales y tests decorativos.

Cómo mejorar: recomendaciones prácticas

1. No busques subir el coverage por subirlo

La calidad está en matar mutantes, no en ejecutar líneas.

2. Revisa cada mutante que sobreviva

Te dice exactamente qué caso no estás testeando.

3. Añade un test por mutante sobrevivido

Es la forma más rápida de mejorar la suite.

4. Evita el sobremockeo

Muchos mutantes sobreviven porque los tests no están verificando comportamiento real.

5. Integra Stryker.NET en CI/CD

Una ejecución nocturna o por módulo es suficiente para mantener control.

Conclusión

He insistido durante años en que code coverage no mide calidad.
A veces lo repito tanto que parece un mantra.
Y aun así, la pregunta vuelve: “¿Qué métrica usamos entonces?”.

La respuesta es clara:

Mutation Testing mide lo que importa:
la capacidad real de tus tests para detectar errores.

  • Muchos tests pueden no aportar valor.

  • Pocos tests bien diseñados pueden matar decenas de mutantes.

  • Un proyecto con 90% de cobertura y 25% de mutation score NO está bien testado.

La única forma de saber si tu suite de pruebas es de calidad
—y no simplemente “cantidad”—
es mutando tu código y viendo si sobreviven los errores simulados.

Si sobreviven… tus tests no están protegiendo nada.