Nunca confíes en tests escritos por IA para validar código escrito por IA
Los tests pasan. El código sigue estando mal. Esta es la trampa: tu asistente de IA escribió el código con una suposición errónea — y luego escribió los tests con la misma suposición errónea. Coinciden entre sí, y ambos están mal. Una suite en verde prueba consistencia, no corrección.
Por qué una sola mente no puede calificar su propia tarea
Los tests unitarios funcionan como red de seguridad porque codifican una segunda opinión independiente sobre lo que el código debería hacer. Cuando un humano escribe tests para su propio código, esa independencia ya está debilitada — por eso existen el code review y QA. Cuando una sola sesión de un LLM escribe ambos lados, la independencia colapsa por completo: la mala lectura de la especificación que produjo el bug está ahí mismo en el contexto, moldeando cada aserción que el modelo escribe después.
Esto no es hipotético. Un estudio empírico sobre flujos de generación de tests con LLMs encontró que los tests generados después de código defectuoso detectan solo el 14% de las fallas, frente al 25% de los tests generados de forma independiente de la implementación — los errores se propagan, e "implementaciones y tests incorrectos son mutuamente consistentes, enmascarando defectos en lugar de revelarlos". Un segundo estudio que midió la influencia del código incorrecto en la generación de tests encontró que los tests generados para código incorrecto sufren una tasa de detección de bugs 47% peor en ejemplos a nivel de repositorio.
Los modos de falla a vigilar
- Condiciones invertidas. El modelo cambia
>=por>en el código, y luego asevera el comportamiento invertido en el test. Ambos coinciden; el bug de frontera llega a producción. - Argumentos intercambiados.
transfer(to, from)en lugar detransfer(from, to)— y el test construye su fixture en el mismo orden intercambiado. - Lavado de snapshots. El modelo ejecuta el código con el bug, observa la salida incorrecta y asevera esa salida como el valor esperado. El test es un espejo, no un juez.
- Aserciones vacías. Tests que verifican "no se lanzó una excepción" o que aseveran sobre un mock que el propio test configuró — 100% de éxito, verificación casi nula.
Reglas prácticas
| Situación | Qué hacer |
|---|---|
| La IA escribió código + tests juntos | Verifica el código contra cada invariante declarado en la especificación, no contra sus propios tests |
| La IA escribió los tests | Lee cada línea assert; verifica los valores esperados a mano o desde la especificación, nunca desde la salida del programa |
| Necesitas tests para código de IA | Genera los tests a partir de los requisitos en una sesión nueva, sin la implementación en el contexto |
| Suite en verde sospechosa | Reproduce el comportamiento tú mismo con una ejecución manual del sistema real |
| Lógica de alto riesgo | Agrega mutation testing o tests basados en propiedades como árbitro independiente |
La regla unificadora: el oráculo — la fuente de "qué es correcto" — debe venir de fuera de la ejecución del modelo que produjo el código. Eso es la especificación, tu propia reproducción, o una verificación mecánicamente independiente.
Movidas avanzadas: independencia mecánica
- Mutation testing. Herramientas como Stryker (JS/TS, C#, Scala) inyectan automáticamente pequeños bugs (mutantes) en tu código y vuelven a ejecutar la suite. Si un mutante sobrevive — los tests siguen pasando — encontraste exactamente el tipo de test débil y autocomplaciente que la IA tiende a escribir. Un reporte de mutantes sobrevivientes es una lista de aserciones por reforzar.
- Tests basados en propiedades. Con una librería como Hypothesis (Python), declaras invariantes ("decode(encode(x)) == x", "la salida siempre está ordenada") y el framework genera cientos de entradas adversariales. Las propiedades vienen de la especificación, no de la implementación, así que no pueden heredar sus suposiciones.
- Separa los roles. Si de todos modos quieres tests escritos por IA, genéralos en una sesión separada solo a partir de los requisitos en lenguaje natural. El estudio de código incorrecto encontró que agregar una descripción en lenguaje natural junto al código mejoró la detección de bugs en +34% — los tests guiados por la descripción se anclan a la intención, no al código con el bug.
- Haz que el test falle primero. Antes de confiar en cualquier test nuevo, rompe deliberadamente el código que cubre y confirma que el test se pone en rojo. Un test que nunca viste fallar no ha probado nada.
En resumen
Nunca dejes que los tests de la IA sean el único juez del código de la IA. Que pasen tests escritos por la misma mente que escribió el bug es acuerdo, no evidencia. Consigue tu oráculo desde afuera: la especificación, tus propias manos, o una corrida de mutation testing que intente matar la suite.
Recursos
- On the risk of coding before testing: An empirical study on LLM-based test generation workflow (arXiv) — el resultado de 14% vs 25% en detección de fallas
- Measuring the Influence of Incorrect Code on Test Generation (arXiv) — 47% peor detección de bugs en repos reales; +34% con descripciones en lenguaje natural
- What is mutation testing? — documentación de Stryker Mutator
- Mutation testing en .NET — Microsoft Learn
- Hypothesis — property-based testing para Python
¿Construyendo con asistentes de AI coding? Yeda AI diseña, audita y lleva a producción sistemas LLM.