Generas un módulo. Funciona cuando haces clic por él. Luego te sientas a añadir tests, porque sabes que deberías, y algo chirría un poco. Cada test que escribes pasa a la primera. No tras un arreglo. A la primera. Te dices que el código era bueno. No lo era. Escribiste los tests después de leer el código, así que los tests codifican lo que el código hace, no lo que debía hacer.
Eso es lo único que cambia de verdad al probar código generado por IA. Casi todo lo demás sigue siendo igual que siempre, y los errores vienen de creer lo contrario.
La intención nunca estuvo en la sala
Un test es una afirmación sobre la intención. Dice «esta entrada debería producir este comportamiento» y falla cuando la realidad no está de acuerdo. Eso solo funciona cuando la intención existe en algún sitio independiente del código. Cuando escribes el código tú, la intención vive en tu cabeza mientras tecleas, y el test que escribes a continuación es una segunda expresión de la misma idea. La redundancia es el punto. Dos testigos de la misma intención, y si se contradicen, uno de los dos está mal.
Con código generado, falta el segundo testigo. El modelo produjo la implementación más probable de tu prompt. Si luego lees esa implementación y escribes tests contra lo que ves, tienes un testigo por duplicado. El código dice X, el test afirma X, coinciden, verde. Has demostrado que el código hace lo que el código hace. No has demostrado nada sobre lo que querías.
Por eso probar código generado de la forma ingenua resulta tan cómodo y sirve tan poco. La fricción que antes cazaba fallos, el momento en que tu test y tu recuerdo de la intención rozaban, desapareció. Nadie en la sala recuerda para qué era la feature. El modelo nunca lo supo. Externalizaste la escritura y la intención se fue con ella.
Qué cambia de verdad
Se mueven tres cosas, y conviene nombrarlas con precisión justamente porque todo lo demás se queda quieto.
La fuente de la verdad se mueve antes. Cuando escribes código, el código puede servir como registro tosco de su propia intención porque tú sostenías la intención al escribirlo. Cuando el que escribe es un agente, la intención hay que capturarla antes de generar, o se evapora. Los criterios de aceptación, el comportamiento acordado, los ejemplos de salida correcta e incorrecta, todo eso tiene que existir como artefacto al que el test pueda apuntar. No en el chat. En algo duradero.
El orden de escritura se mueve. Si escribes el test después de leer el código generado, el código contamina el test. Así que el test, o al menos el comportamiento que codifica, tiene que salir de la especificación, no del diff. Describes qué significa «correcto», dejas que el agente genere, y contrastas la generación contra un test que nunca vio la implementación. La misma disciplina que el desarrollo test-first, por una razón más afilada: no para dar forma a tu diseño, sino para impedir que el código se escriba su propio examen.
El volumen se mueve. Una sola persona puede generar en una tarde el código de una semana. Cada uno de esos cambios necesita su comportamiento fijado. Ya no pruebas el código que escribiste despacio, pruebas una avalancha de código que ojeaste. La cobertura que antes se acumulaba como efecto secundario de trabajar ahora tiene que ser un gate deliberado, porque la parte de trabajar se aceleró y la de entender no.
Qué no cambia, te vendan lo que te vendan
Un test sigue demostrando solo lo que ejercita. Una suite en verde sobre código generado significa lo mismo que significó siempre, que los caminos para los que escribiste aserciones se comportaron como afirmaste, y nada sobre los caminos que no. El modelo no vuelve seguras tus ramas sin probar. Si acaso escribe más ramas, más rápido, así que la distancia entre «los tests pasan» y «la feature es correcta» se ensancha, no se estrecha.
La cobertura sigue mintiendo sobre lo que te importa. La cobertura de líneas te dice que una línea se ejecutó, no que una aserción habría cazado su fallo. Puedes ejecutar todas las líneas de un módulo generado sin afirmar casi nada, y el número queda estupendo. La advertencia de Martin Fowler de que la cobertura de tests es una señal útil y un objetivo pésimo aplica aquí con toda su fuerza, porque los agentes son buenísimos produciendo tests que suben el número y no comprueban nada. Pídele a uno que «añada tests» y cuenta cuántos afirman que una función retorna sin lanzar.
La pirámide sigue en pie. Un montón de tests end-to-end sobre código generado es tan lento y frágil como siempre, y una base de tests unitarios rápidos que fijan comportamiento de verdad sigue siendo lo que te deja cambiar cosas sin miedo. La pirámide de tests práctica no quedó derogada porque ahora teclee una máquina. Y el código que se prueba a sí mismo, la propiedad de que el sistema pueda demostrarse correcto a demanda, vale más cuando no escribiste el sistema, no menos. Es lo único entre tú y una base de código que tienes que creer por fe.
El test que el agente escribe para su propio código
Hay un modo de fallo concreto que merece nombre porque ya es el de por defecto. Le pides a un agente que implemente una feature y que escriba tests para ella. Hace las dos cosas, en el mismo turno, desde el mismo contexto. Los tests pasan. Te sientes cubierto.
No lo estás. Tienes un sistema donde el mismo proceso escribió la implementación y el examen, sosteniendo una única idea del comportamiento, y se autocorrigió. Si el modelo malentendió el requisito, lo malentendió de forma consistente, y los tests codifican el malentendido como resultado esperado. El verde es real. La corrección no. Es el primo cercano del agente que informa «los tests pasan» sin ejecutar nada, solo que peor, porque aquí los tests corrieron de verdad y pasaron de verdad, y siguen sin valer como evidencia de corrección.
El arreglo es la separación. El comportamiento lo especificas tú, o se genera en un paso aparte desde otro encuadre, y se fija antes de que exista la implementación. Luego la implementación se genera contra eso. El examen tiene que ser anterior a la respuesta, o no es un examen.
Cómo mantener veraces los tests con código generado
Escribe las aserciones desde la especificación, no desde el diff. Si te descubres abriendo el fichero generado para decidir qué afirmar, para. Estás a punto de ratificar el código en vez de probarlo. Vuelve a lo que la feature debía hacer.
Fija comportamiento en la frontera, no en las tripas. El código generado se reescribe. Si tus tests están soldados a la estructura privada que el agente produjo, se rompen en cada regeneración y aprendes a ignorarlos. Prueba el contrato observable, la entrada, la salida y el efecto, para que los tests sobrevivan a que el agente reescriba el medio.
Guarda al menos un test que el agente nunca vio. Cuando generes la implementación y los tests juntos, añade después tu propio caso, desde la spec, a mano. Un testigo imparcial basta para cazar toda una clase de disparates seguros de sí mismos.
Trata la cobertura como un suelo con dientes, no como un trofeo. Un número por debajo del cual nadie puede bajar es un gate. Un número en un panel es decoración. Si el gate se satisface con tests que no afirman nada, el gate también es decoración.
Nada de esto es teoría nueva de testing. Es teoría vieja aplicada a una situación en la que la intención se marchó del edificio. Toda la disciplina de verificar código generado por IA se apoya en esto: un test solo es evidencia cuando se escribió para defender un comportamiento que existe fuera del código que prueba. En cuanto el test empieza a describir la implementación en vez del requisito, vuelves a confiar en la frase segura de una máquina, disfrazada de verde.
Dónde encaja PaellaDoc
La razón de que los tests deriven a ratificar el código es que la intención no tiene dónde vivir. El chat se va, el ticket dice «añadir búsqueda» y seis semanas después nadie sabe qué significaba «búsqueda correcta». PaellaDoc guarda los criterios de aceptación, las decisiones y el comportamiento como artefactos duraderos junto al código, para que el test tenga algo a lo que apuntar que la implementación nunca tocó. El agente escribe el código. Los criterios que acordaste antes escriben el examen. Cuando esos dos discrepan, te enteras antes de que se publique, que es el único momento en que enterarse sale barato.
Preguntas frecuentes
¿Probar código generado por IA es distinto de probar el que escribiste tú?
Cambia una cosa: la intención nunca estuvo en la cabeza de nadie. Cuando escribes código, la intención vive en tu mente y el test es una segunda expresión independiente de ella. Con código generado falta ese segundo testigo, así que si escribes los tests después de leer la implementación, codifican lo que el código hace en vez de lo que debía hacer. Casi todo lo demás de probar software sigue exactamente igual.
¿Por qué mis tests siempre pasan a la primera con código generado por IA?
Porque los escribiste después de leer el código, así que describen la implementación en vez del requisito. El código dice X, el test afirma X, coinciden, verde. Has demostrado que el código hace lo que el código hace y nada sobre lo que querías. El arreglo es escribir las aserciones desde la especificación, no desde el diff, para que el test defienda un comportamiento que existe fuera del código que comprueba.
¿Debe la misma IA escribir el código y sus tests?
No en el mismo paso desde el mismo contexto. Si el modelo malentendió el requisito, lo malentiende de forma consistente, y los tests codifican ese malentendido como resultado esperado. El verde es real, la corrección no. Sepáralos: especifica el comportamiento tú y fíjalo antes de que exista la implementación, luego genera contra eso. Guarda al menos un test que el agente nunca vio. El examen tiene que ser anterior a la respuesta.
¿Una cobertura alta significa que el código generado por IA es correcto?
No. La cobertura te dice que una línea se ejecutó, no que una aserción habría cazado su fallo. Puedes ejecutar todas las líneas de un módulo generado sin afirmar casi nada mientras el número queda estupendo, y los agentes son buenísimos produciendo tests que suben la cobertura y no comprueban nada real. Trata la cobertura como un suelo con dientes, un gate por debajo del cual nadie baja, no como un trofeo en un panel. Si tests que no afirman nada satisfacen el gate, el gate es decoración.