Saltar al contenido principal
onext technology
DevOps 8 septiembre 2026 - 18 min de lectura

Tests generados por IA: un test escrito desde el código no lo prueba, lo describe

La cobertura sube, el panel se pone verde y la capacidad de detectar un fallo se queda donde estaba. No es un problema del modelo: es que un test derivado del código sólo puede afirmar lo que el código hace, y eso incluye lo que hace mal.

Jordi García
Tech Lead en onext
Un inspector de calidad comprueba al anochecer una pieza metálica contra un patrón de referencia con un calibre, como metáfora del oráculo externo que necesita un test generado por IA

Abre el último pull request en el que el asistente escribió los tests. Busca en el código que prueban una comparación cualquiera —un >, un >=, un ==— y cámbiala por la contraria. No toques los tests. Ejecuta la suite.

Si sigue en verde, ya sabes qué mide vuestra cobertura.

El experimento cuesta diez minutos, no necesita instalar nada y tiene una propiedad que casi ningún dato de calidad tiene: no admite discusión sobre la metodología, porque lo ha visto todo el mundo en la misma pantalla. Y es la versión artesanal de una técnica con cuarenta años de literatura detrás, el mutation testing, que es de lo que va esta pieza.

El punto no es que los tests generados sean malos. Es que se están leyendo como si respondieran a una pregunta que no responden. Ya hemos hablado de qué pasa con el review cuando el autor deja de existir como interlocutor y de las dependencias que entran disfrazadas de una línea. Los tests son la tercera cosa que llega en el mismo pull request y la que mejor disfraz lleva, porque llega con un número que sube.

Cuatro condiciones, y la cobertura sólo comprueba la primera

Para que un fallo en una línea de código acabe apareciendo como un test en rojo tienen que cumplirse cuatro cosas seguidas. La formulación es clásica en la literatura de testing y Parsai y Demeyer la resumen así en su estudio industrial: la línea defectuosa tiene que ejecutarse; tiene que alterar el estado del programa; esa alteración tiene que propagarse hasta la salida; y el test tiene que observar esa salida y decidir que está mal.

Cuatro eslabones. La cobertura mide el primero. Sólo el primero.

Condición Qué tiene que pasar Dónde falla un test generado desde el código
Ejecución La línea defectuosa se ejecuta durante el test Casi nunca. Es justo lo que el modelo hace bien: llegar a todas las ramas. Aquí es donde sube el número.
Infección La ejecución deja el estado del programa en un valor distinto del correcto A veces. Depende de si los datos de entrada elegidos tocan el borde donde el defecto cambia algo, y los valores generados tienden al caso central.
Propagación Ese estado incorrecto llega hasta una salida observable A veces. Los tests muy unitarios con dobles por todas partes cortan la propagación antes de que salga nada.
Revelación El test observa la salida y decide que está mal Aquí es donde se rompe. La aserción se escribió mirando la salida que el código produce hoy. Si esa salida es incorrecta, la aserción la da por buena.

Las cuatro condiciones, en la formulación que recogen Parsai y Demeyer. La tercera columna es lectura propia sobre el comportamiento típico de un test derivado del código.

Puesto así se entiende por qué el número puede subir sin que la red mejore. Un test que ejecuta una línea y no comprueba nada relevante sobre lo que esa línea produjo cuenta igual para la cobertura que uno que sí lo comprueba. La herramienta de cobertura no distingue: no está diseñada para distinguir. La documentación de PIT lo dice sin rodeos — la cobertura de líneas «mide sólo qué código ejecutan tus tests. No comprueba que tus tests sean capaces de detectar fallos en el código ejecutado».

El oráculo hereda aquello de lo que nace

La parte de un test que decide si algo está bien o mal tiene nombre propio en la literatura: el oráculo. Es la aserción, y es la única parte que hace trabajo de verificación; todo lo demás es montaje. La pregunta útil, entonces, no es quién escribió el test, sino de dónde salió su oráculo.

Aquí está el hallazgo que ordena todo lo demás. Bodicoat, Jahangirova y Terragni abren su estudio de enero de 2026 constatando que las técnicas existentes de generación de tests «generan principalmente oráculos de regresión que se predican sobre el comportamiento implementado de la clase bajo prueba» y que, por tanto, «no abordan el problema del oráculo: la dificultad de distinguir el comportamiento correcto del incorrecto».

Un oráculo de regresión es una afirmación sobre el estado actual. Dice: esto hace hoy esto. Es enormemente útil —es lo que te avisa de que un refactor cambió algo sin querer— pero es una foto, no un juicio. Si el código tenía un fallo el día que se generó el test, el test lo fotografía junto a todo lo demás y lo protege a partir de entonces. La suite deja de ser una red y pasa a ser un espejo: confirma que el código sigue siendo el que era.

La distinción que sostiene el resto del artículo: un test escrito desde el código puede detectar cambios; sólo un test escrito desde la intención puede detectar errores. Son dos trabajos distintos y la cobertura los cuenta igual, porque cuenta líneas ejecutadas y ambos ejecutan las mismas.

Hay una frustración muy repetida que encaja aquí y que casi siempre se cuenta en otro contexto. En la encuesta de Stack Overflow de 2025, con más de 49.000 respuestas, la queja número uno sobre las herramientas de IA —el 66%, muy por delante de la segunda— son «las soluciones de IA que están casi bien, pero no del todo». En un fragmento de lógica, «casi bien» se ve: falla, o hace algo raro, y alguien lo mira. En una aserción, «casi bien» es verde. Un oráculo equivocado no se comporta como un error: se comporta como una confirmación.

Meta publicó el embudo completo, y es el dato más honesto que hay

Conviene mirar el mejor caso documentado antes de opinar sobre el caso medio. Meta describió en 2024 su herramienta TestGen-LLM, que no genera tests desde cero sino que mejora tests existentes escritos por humanos, y —esto es lo relevante— sólo acepta los que superan una cadena de filtros que garantizan una mejora medible sobre la suite original. La motivación declarada de esos filtros es eliminar los problemas derivados de la alucinación del modelo.

En la evaluación sobre los productos Reels y Stories de Instagram, el embudo quedó así: el 75% de los casos generados compilaba, el 57% pasaba de forma fiable y el 25% aumentaba la cobertura. En los test-a-thons de Instagram y Facebook mejoraron el 11,5% de todas las clases donde se aplicó, y los ingenieros de Meta aceptaron el 73% de las recomendaciones para producción.

Uno de cada cuatro. Con un equipo que sabe lo que hace, un filtro automático delante y la escala de Meta detrás. Ese número es el suelo realista de la conversación, y es una buena noticia: significa que con filtro esto funciona, y el 73% de aceptación humana lo confirma.

Pero merece la pena leer los tres filtros por lo que preguntan. ¿Compila? ¿Pasa de forma estable? ¿Aumenta la cobertura? Son tres preguntas razonables y ninguna de las tres es ¿detectaría este test un fallo?. El filtro más estricto publicado por la industria se detiene, por diseño, en el primero de los cuatro eslabones. No es un descuido de Meta —la cobertura es medible, automática y barata, y es infinitamente mejor que aceptar lo que diga el modelo— pero marca dónde está hoy el techo del enfoque.

Por qué la cobertura no iba a servir para esto, y se sabe desde 2014

El trabajo de referencia es de Laura Inozemtseva y Reid Holmes, presentado en la International Conference on Software Engineering de 2014. Generaron 31.000 suites de test sobre cinco sistemas Java reales —Apache POI, Closure Compiler, HSQLDB, JFreeChart y Joda Time, de hasta 724.089 líneas de código—, midieron su cobertura de sentencias, de decisiones y de condición modificada, y evaluaron su eficacia real inyectando fallos con mutation testing.

Tres conclusiones, y las tres siguen incómodas doce años después. La correlación entre cobertura y eficacia es de baja a moderada cuando se controla el número de casos de la suite. La fuerza de esa relación varía entre sistemas, así que no es seguro asumirla en el tuyo. Y las formas más sofisticadas de cobertura no aportan más información que la simple, pese a costar más de medir y de satisfacer.

La frase con la que cierran es la que debería estar pegada en cualquier panel de calidad: la cobertura, aun siendo útil para identificar partes infratestadas de un programa, no debería usarse como objetivo de calidad, porque no es un buen indicador de la eficacia de una suite. Los sistemas que estudiaron iban del 27% de cobertura de sentencias en HSQLDB al 91% en Joda Time, y el número por sí solo no ordenaba las suites por capacidad de detección.

Nada de esto es nuevo ni tiene que ver con la IA. Lo que ha cambiado es el volumen: cuando escribir tests costaba tiempo humano, la cobertura crecía despacio y más o menos en proporción al pensamiento invertido. Ese acoplamiento accidental entre el número y el esfuerzo era lo que hacía que la métrica pareciera funcionar. Generar tests en masa lo rompe, y deja la métrica a la vista tal como es.

La medida que sí se mueve

El mutation testing responde exactamente a la pregunta que la cobertura no responde. El procedimiento, en la formulación de PIT: se siembran fallos automáticamente en el código y se ejecutan los tests; si los tests fallan, el mutante está muerto; si pasan, el mutante sobrevive. La proporción de mutantes muertos es el mutation score, y es una medida de la capacidad de detección, no de la superficie recorrida.

La documentación de Stryker lo ilustra con el caso que más se parece al del principio de este artículo: una comprobación de edad. Si al cambiar la condición ningún test se pone rojo, probablemente falte un test que compruebe explícitamente el borde. El mutante superviviente no es una métrica abstracta: es un test que falta, con nombre y apellidos, señalado con el dedo.

La propiedad importante es que este número no se puede inflar con tests que ejecutan código sin comprobar nada. Un test sin aserciones útiles sube la cobertura y no mata un solo mutante. Es, por construcción, inmune al modo de fallo que trae la generación masiva.

  Cobertura Mutation score
Qué pregunta ¿Qué código ejecutan mis tests? ¿Detectarían mis tests un cambio en ese código?
Eslabones que cubre El primero Los cuatro, por construcción
Se puede inflar con Tests sin aserciones útiles Nada equivalente
Coste de medir Segundos; ya está en vuestro CI Alto: una pasada completa es trabajo de fin de semana
Ruido propio Bajo Mutantes equivalentes: del 0,4% al 35% según el proyecto
Para qué usarla Mapa de huecos Nota del examen, sobre el código que importa

El rango de mutantes equivalentes procede de los cinco sistemas medidos por Inozemtseva y Holmes: 0,4% en HSQLDB, 35% en Apache POI.

Lo que cuesta de verdad, dicho sin adornos

El mutation testing no se ha adoptado masivamente por una razón sólida: es caro. Parsai y Demeyer se propusieron medir exactamente eso sobre cuatro sistemas open source y uno industrial, e investigaron las tres objeciones habituales: que es difícil de integrar en el sistema de construcción, que la cobertura de ramas ya es «suficientemente buena» y que la sobrecarga de tiempo es inasumible.

Los números del caso industrial: 12.825 mutantes para 38.000 líneas de código. Definieron como aceptable un trabajo que quepa entre el viernes a las 18h y el lunes a las 8h —62 horas— para que los resultados estén listos antes de la reunión de sprint. Cuando la pasada completa no entra, aplican muestreo de mutantes reduciéndolos al 34,7%, y citan trabajo previo según el cual muestras de hasta el 50% siguen dando resultados fiables. Su conclusión es que la cobertura de mutación revela debilidades adicionales que la cobertura de ramas no ve, con una sobrecarga asumible.

Hay dos avisos honestos que sacar de ahí. El primero es que esto no es gratis y no va en cada commit: la forma de meterlo en un ciclo corto es mutar sólo las clases que toca el diff. El segundo lo dicen los propios autores y va contra la intuición cómoda: no encontraron evidencia suficiente para confirmar que la cobertura de ramas sea un buen estimador de la cobertura de mutación en sistemas complicados. Es decir, tampoco vale usar la cobertura como aproximación barata al número que sí importa.

Y el ruido propio de la técnica es real: los mutantes equivalentes —cambios sintácticos que no alteran el comportamiento y que por tanto ningún test puede detectar— iban del 0,4% al 35% según el proyecto en el estudio de Inozemtseva y Holmes. Distinguirlos exige inspección manual. Quien venda el mutation score como una métrica limpia y automática está vendiendo otra cosa.

La palanca no es el modelo: es la entrada

Si el oráculo hereda aquello de lo que nace, hay una intervención evidente antes de tocar nada más: cambiar de qué nace. En lugar de darle al modelo la implementación y pedirle tests, darle lo que el código debía hacer.

Ma y Eisty lo probaron en julio de 2026 de la forma más limpia posible: generar oráculos a partir únicamente de requisitos en lenguaje natural, sin acceso al código fuente ni a ejemplos de entrada, y enfrentarlos a diez bugs reales del proyecto Lang de Defects4J con cinco modelos distintos. El resultado que importa es de dirección: los oráculos generados se alinean más con el requisito que con el comportamiento implementado del sistema.

Es un estudio pequeño —diez bugs, cinco modelos, un solo proyecto— y hay que tratarlo como una señal, no como una demostración cerrada. Pero apunta a lo mismo que la práctica: el modelo no tiene forma de saber que una salida está mal si lo único que le has enseñado es esa salida. Dale el criterio y podrá contrastarlo; dale el resultado y sólo podrá copiarlo.

Esto es exactamente el argumento del Spec-Driven Development aplicado al lado que casi nadie mira. Cuando existe una especificación escrita, versionada y legible por el agente, no sólo sirve para generar el código: es el único material del que puede salir un test que juzgue en vez de describir. Lo mismo que la ingeniería de contexto resuelve para la generación, lo resuelve para la verificación — y por el mismo motivo, porque el problema nunca fue la habilidad del modelo sino lo que tenía delante.

Es la misma lógica que ya aplicamos al evaluar sistemas de IA: un golden set vale porque las respuestas correctas las fijó alguien con criterio antes de ver lo que el sistema producía, y por eso puede decir si algo funciona mejor en lugar de sólo si cambió. Un test es un golden set de una función. Si lo generas mirando la salida, has construido el conjunto de evaluación a partir de las respuestas del examinando.

El orden en que esto se arregla

Tres decisiones, y el orden importa más que ninguna de ellas por separado.

Primero, medir una vez. Una clase, el experimento del principio, diez minutos. No para tener un dato representativo, sino para convertir una discusión de opiniones en una observación compartida. Mientras la conversación sea «yo creo que nuestros tests son flojos», no se mueve nada; en cuanto es «cambié este signo y la suite pasó», se mueve sola.

Segundo, separar los dos trabajos. La suite generada se queda donde está y hace lo que sabe hacer: detectar cambios no intencionados, que para refactorizar con IA es justo lo que hace falta. Encima de eso, un conjunto pequeño de tests escritos desde el criterio —los bordes, las reglas de negocio, lo que la especificación dice que tiene que pasar— que es el que juzga. No compiten: uno es el espejo y el otro es la red, y hacen falta los dos.

Tercero, cambiar el objetivo. Si en el panel hay un umbral de cobertura, quitarlo del código donde vive la lógica de negocio y sustituirlo por mutation score sobre el diff. La cobertura se queda como lo que Inozemtseva y Holmes dijeron que era: un mapa para encontrar lo que nadie toca. Un umbral de cobertura sobre una suite generada es la única combinación que garantiza el peor resultado posible — el número sube por construcción y no informa de nada.

Nada de esto exige una herramienta nueva la primera semana. Exige dejar de leer un número como si respondiera a una pregunta que nunca respondió, y eso ya cambia qué se pide en la siguiente conversación sobre calidad y qué se pone en el panel de métricas del equipo. Un asistente que escribe tests es una buena noticia: multiplica el trabajo de andamiaje, que era el aburrido. Sólo hay que dejar de pedirle que además sea el juez, porque para eso necesita algo que sólo vosotros tenéis — saber qué se le pidió al código antes de que existiera.

La calidad del código en la era del AI coding se ha discutido sobre todo mirando el código. Merece la pena mirar también lo que lo vigila: si la suite creció al mismo ritmo que el código y ninguna de las dos cosas la escribió alguien que supiera qué tenía que pasar, la señal verde de cada mañana lleva un tiempo sin significar lo que creéis que significa.

Preguntas frecuentes

¿Entonces la cobertura no sirve para nada?

Sirve para lo que siempre sirvió: encontrar el código que ningún test toca. Eso es información real y barata de obtener. Lo que no hace es decirte si los tests que sí tocan el código detectarían un fallo en él, porque sólo mide la primera de las cuatro condiciones que tienen que cumplirse para que un fallo se revele. Inozemtseva y Holmes lo dejaron escrito en 2014 tras generar 31.000 suites sobre cinco sistemas Java de hasta 724.089 líneas: la correlación entre cobertura y eficacia es de baja a moderada cuando se controla el número de casos, y la conclusión que sacan es que la cobertura es útil para identificar partes infratestadas pero no debería usarse como objetivo de calidad. La diferencia práctica: úsala como mapa de huecos, no como nota del examen.

¿El mutation testing no es demasiado lento para CI?

Para pasarlo entero en cada commit, sí. Parsai y Demeyer lo midieron sobre un sistema industrial real: 12.825 mutantes para 38.000 líneas de código, con un presupuesto de tiempo definido como el hueco entre el viernes a las 18h y el lunes a las 8h. Es un trabajo de fin de semana, no de pull request. La forma de meterlo en un ciclo corto es no pasarlo entero: mutar sólo las clases que toca el diff, y usar muestreo de mutantes cuando ni así entra —los mismos autores reducen la muestra al 34,7% conservando la información, y citan trabajos donde muestras del 50% siguen dando resultados fiables. La versión de diez minutos es hacerlo a mano sobre una clase, que es como conviene empezar antes de montar nada.

Si el modelo escribe el test a partir de la especificación, ¿no acaba igual de sesgado?

Sesgado hacia otra cosa, y esa es toda la diferencia. Un test derivado del código sólo puede afirmar lo que el código hace; si el código está mal, el test consagra el error. Un test derivado de la especificación afirma lo que se pidió, y cuando ambos discrepan aparece el fallo. Ma y Eisty lo midieron sobre diez bugs reales de Defects4J generando oráculos únicamente desde requisitos en lenguaje natural, sin código a la vista: los oráculos se alinean más con el requisito que con el comportamiento implementado. Es un estudio pequeño —diez bugs, cinco modelos— y conviene tratarlo como dirección, no como prueba cerrada. Pero la dirección es la que importa: el oráculo hereda aquello de lo que nace.

¿Vale la pena que la IA escriba tests, entonces?

Sí, con un filtro delante y sin esperar que el filtro sea la cobertura. El dato honesto lo publicó Meta con TestGen-LLM: sobre Reels y Stories de Instagram, el 75% de los casos generados compilaba, el 57% pasaba de forma fiable y el 25% aumentaba la cobertura. Uno de cada cuatro llegaba al final de un embudo diseñado por gente que sabe lo que hace, y aun así los tres filtros preguntan si el test compila, si es estable y si ejecuta código nuevo — ninguno pregunta si detectaría un fallo. Con ese matiz puesto, el resultado de campo es bueno: mejoraron el 11,5% de las clases donde se aplicó y los ingenieros de Meta aceptaron el 73% de las recomendaciones para producción.

¿Qué hago con la suite generada que ya tenemos?

No la tires: mídela antes de decidir. La suite generada casi siempre aporta algo real —cubre caminos que nadie había tocado y documenta el comportamiento actual, que para refactorizar vale— y su problema no es que sea falsa, sino que se está leyendo como si fuera otra cosa. El orden que funciona es medir el mutation score de dos o tres clases con lógica de negocio de verdad, ver qué mutantes sobreviven, y escribir a mano sólo los tests que matan a esos supervivientes. Suelen ser pocos y suelen ser los de las condiciones de borde. Lo que no hay que hacer es fijar un objetivo de cobertura para la suite generada: sube el número sin mover la capacidad de detección, que es exactamente el fallo que se quería corregir.

¿Por dónde empiezo esta semana sin montar un programa?

Por el experimento de diez minutos sobre una sola clase. Se elige la clase con más lógica de negocio del último trimestre, se cambia una comparación en el código —un signo, una condición de borde— sin tocar los tests, y se ejecuta la suite. Si sigue verde, ya tienes la conversación: no hace falta ninguna herramienta ni ningún presupuesto para tenerla, y el resultado no admite discusión metodológica porque lo ha visto todo el mundo en la misma pantalla. A partir de ahí, la decisión de si merece la pena instalar PIT o Stryker deja de ser teórica. El error es empezar comprando la herramienta: el dato que mueve a un equipo es el mutante que sobrevivió en su propio código.

Fuentes citadas

Jordi García
Escrito por
Jordi García
Tech Lead en onext

Jordi García es Tech Lead en onext. Trabaja en llevar la IA a producción gobernada en equipos de desarrollo y de producto —con Spec-Driven Development, ingeniería de contexto y verificación humana en cada paso— y firma los insights técnicos de onext sobre método, calidad y coste de la IA aplicada.

LinkedIn →

¿Cuántos mutantes sobreviven hoy en las clases donde vive vuestra lógica de negocio?

Medimos ese número en vuestro repositorio, separamos la suite que detecta cambios de la que detecta errores, y dejamos la especificación escrita donde el asistente la lee antes de generar un solo test.

Ver cómo trabajamos

Sin plataforma nueva. Sin parar entregas.