Un test que pasa contra código vacío no prueba nada
¿Tus tests seguirían pasando si borraras el código de producción? Entonces no están probando nada. Los asistentes de IA escriben tests rápido — decenas en un minuto — y una suite en verde se siente como seguridad. Pero verde solo significa "ninguna aserción falló". Un test que no puede fallar contra una implementación vacía es un test que no puede atrapar el bug que esa implementación vacía representa. Es teatro.
El problema del falso verde
La cobertura de líneas te dice qué código ejecutaron tus tests, no si notarían una falla en él. El proyecto PIT lo dice sin rodeos: "Traditional test coverage measures only which code is executed by your tests. It does not check that your tests are actually able to detect faults in the executed code." Un test puede recorrer cada rama, afirmar algo vacío como assert.ok(true) y reportar 100% de cobertura sin verificar nada.
Los tests generados por IA agravan el problema, no porque el modelo sea descuidado, sino porque optimiza para que la suite pase. Si el camino más rápido al verde es una tautología — afirmar que el mock fue llamado con el valor que el propio test configuró, o atrapar e ignorar la excepción bajo prueba — obtendrás una tautología. Pediste tests; recibiste un check verde.
La auditoría de cinco minutos
Demuestra que tus tests pueden fallar. Toma el módulo que la IA acaba de "cubrir" y rómpelo a propósito:
# 1. Gut the implementation (stub every function to return nothing)
# 2. Rerun the suite
pytest tests/test_orders.py
# Real tests: go red immediately.
# Theater: still green. Delete or rewrite them.
Reemplaza el cuerpo de la función por return None (o pass, o throw new Error()), vuelve a correr la suite y separa los tests en dos pilas. Rojo significa que el test se gana su lugar. Verde significa que nunca dependió del comportamiento. Es la versión manual del mutation testing, y toma cinco minutos por módulo.
Señales que predicen un verde falso
| Síntoma | Lo que suele significar |
|---|---|
assert.ok(true) / assertTrue(True) | Placeholder que nunca puede fallar |
| El test afirma que el mock devolvió lo que se le dijo al mock que devolviera | Prueba el mock, no el código |
try/catch que se traga la ruta de fallo | La aserción nunca se alcanza |
| Snapshot actualizado cada vez que falla | El test ratifica cambios, no los verifica |
| Bug corregido la semana pasada, sin nuevo test rojo-luego-verde | La regresión puede volver en silencio |
Esa última fila es el hábito más barato con el mayor retorno: cada corrección de bug se entrega con un test que falló antes del fix y pasa después. Si nunca lo viste en rojo, no sabes si funciona.
Automatízalo: mutation testing
El truco de borrar el código, industrializado. Las herramientas de mutación siembran fallas pequeñas — cambian < por <=, suman 1 a un literal, intercambian break y continue — y luego corren tu suite. Si los tests fallan, el mutante muere (killed); si pasan, sobrevive (survived), y cada sobreviviente marca un punto donde un bug real pasaría sin ser detectado.
- Python —
mutmut runmuta tu código y ejecuta pytest;mutmut browsemuestra los sobrevivientes en una interfaz de terminal. - JavaScript/TypeScript, C#, Scala — Stryker reporta un mutation score: el porcentaje de mutantes que tus tests mataron.
- Java/JVM — PIT se integra con Maven y Gradle y superpone los resultados de mutación sobre la cobertura de líneas.
- Rust —
cargo mutantsencuentra "places where bugs can be inserted without causing any tests to fail".
Esto no es académico. Google integró mutation testing en su code review obligatorio: su sistema ha sido usado por ~6,000 ingenieros, mostrando mutantes sobrevivientes en los diffs que escriben y revisan.
Trucos avanzados
- Audita la salida de la IA por defecto. Trata "los tests pasan" como el inicio de la revisión, no el final. Pídele al propio asistente que reemplace la implementación por un stub y vuelva a correr la suite — es un chequeo de un solo prompt.
- Muta solo el diff. Las corridas de mutación sobre todo el código son lentas; el enfoque de Google analiza solo las líneas cambiadas. Corre las herramientas de mutación sobre los archivos tocados en el PR.
- Prohíbe los placeholders mecánicamente. Una regla de lint o un grep en CI para tautologías tipo
assert.ok(true)cuesta una línea y nunca duerme. - Primero rojo, después verde. Para cualquier test nuevo — humano o de IA — míralo fallar una vez antes de confiar en su verde.
Recursos
- mutmut — mutation testing para Python
- Stryker — mutation testing para JS/TS, C# y Scala
- PIT — mutation testing para Java y la JVM
- cargo-mutants — mutation testing para Rust
- State of Mutation Testing at Google (paper de investigación)
Haz que los tests se ganen su verde. Este artículo acompaña al reel #077 de la serie Yeda AI Tips — tips breves y verificados contra fuentes para desarrolladores que construyen con IA.