Escribir tests unitarios con IA: guía práctica con agentes

Cómo escribir tests unitarios con IA que detecten bugs: tests de caracterización, huecos de cobertura, mutation testing y un agente que prueba mientras otro programa.

Solviera Teknoloji 13 min de lectura

Si quieres escribir tests unitarios con IA, conseguir tests es lo fácil. Pide a cualquier agente de código «tests para este módulo» y en pocos minutos tendrás cuarenta tests en verde. Lo difícil es conseguir tests que fallen cuando el código está mal. Un agente que lee una función y luego escribe aserciones sobre lo que esa función devuelve, en el fondo, ha escrito la función por segunda vez.

Esta guía explica cómo sacar tests útiles de agentes de código como Claude Code, Codex o Gemini CLI: qué pedirles, cómo detectar tests que no demuestran nada, cómo medirlos con cobertura y mutation testing, y cómo repartir el trabajo para que el agente que escribe el código no pueda doblar los tests a escondidas.

Por qué fallan los tests escritos por IA

Los agentes son muy buenos con la parte mecánica del testing: fixtures, factorías, mocks, casos parametrizados, el esqueleto de un archivo de tests nuevo. Donde patinan es justo en lo que da valor a un test: el valor esperado.

Cuando un agente escribe un test, normalmente tiene la implementación abierta en su contexto. Lo natural es leer el código, deducir qué devuelve y afirmar exactamente eso. Si el código tiene un bug, el test lo deja grabado en piedra. Está en verde, cubre las líneas y seguiría en verde aunque nunca arreglaras el bug.

Hay unas cuantas formas típicas de este problema:

  • Lógica en espejo. El test calcula el valor esperado con la misma fórmula que el código: expect(price(100, 0.2)).toBe(100 - 100 * 0.2). Si la fórmula está mal, ambos fallan juntos y nadie se entera.
  • Mockear al sujeto. Lo que se prueba está mockeado, así que el test comprueba el mock.
  • Aserciones que solo miran la llamada. expect(sendEmail).toHaveBeenCalled() sin comprobar qué se envió ni a quién.
  • Snapshot de todo. Un snapshot enorme que nadie lee y que se actualiza con -u cada vez que falla.
  • Aserciones que no pueden fallar. expect(result).toBeDefined() sobre una función que siempre devuelve un objeto.
  • Editar el test para que pase. El código no pasa un test y el agente «arregla» el valor esperado en lugar del código.

Nada de esto es exclusivo de la IA. Las personas también lo hacen. Los agentes simplemente lo producen más rápido y en lotes más grandes, así que necesitas un proceso que lo detecte.

Empieza por el comportamiento, no por el código

La mejora más grande es decirle al agente qué se supone que hace el código y mantener los valores esperados fuera del alcance de la implementación.

Un prompt flojo:

Escribe tests unitarios para src/billing/discount.ts.

Uno mejor:

Escribe tests unitarios para applyDiscount en src/billing/discount.ts. Reglas: un descuento porcentual nunca deja el total por debajo de cero; los descuentos no se acumulan, gana el mayor; un código caducado se ignora, no es un error; los importes se redondean a 2 decimales, mitad hacia arriba. Escribe los valores esperados a mano a partir de estas reglas, sin ejecutar ni leer la implementación. Si una regla parece no coincidir con el código actual, escribe el test igualmente y dime qué tests fallan.

Cambiaron tres cosas. Las reglas vienen de ti, o de un ticket, una especificación o la documentación. Los valores esperados son literales calculados a partir de las reglas. Y se permite que un test falle: puede ser un bug real, que es precisamente para lo que sirven los tests.

Algunos hábitos más que ayudan:

  • Pide primero la lista de casos. «Enumera los casos que vas a probar, uno por línea, antes de escribir código.» Así añades los casos límite que se le escaparon (entrada vacía, unicode, zonas horarias, el límite exacto en cero) antes de que exista una sola línea.
  • Nombra los límites. Los agentes son buenos con los casos límite cuando señalas la dimensión: «entradas en el límite, justo por debajo y justo por encima de cada uno».
  • Di qué no se mockea. «No mockees nada dentro de src/billing; mockea solo el cliente HTTP.» El exceso de mocks es el origen de muchos tests tautológicos.
  • Un comportamiento por test. Tests con un nombre claro como ignores_expired_codes se revisan mucho mejor que un test con doce aserciones.

Tests unitarios, de integración y end-to-end

El mismo agente puede escribir los tres tipos, pero las instrucciones cambian.

Tests unitarios

Rápidos, aislados y muchos. Aquí es donde más tiempo ahorran los agentes. Señala al agente tu estilo de tests actual («sigue tests/billing/tax.test.ts») para no acabar con una librería de helpers nueva cada vez. Pon el comando de tests en tu CLAUDE.md o AGENTS.md para que el agente ejecute los tests por su cuenta; más sobre esto en cómo escribir CLAUDE.md y AGENTS.md.

Tests de integración

Tocan una base de datos, una cola o un sistema de archivos reales. Aquí los agentes tienden a tirar de mocks, y eso convierte el test de integración otra vez en un test unitario. Sé explícito: «usa la base de datos de pruebas de docker-compose.test.yml; no mockees la capa de repositorio». Pide también limpieza, para que los tests no dependan de lo que dejó el anterior.

Tests end-to-end

Para tests de navegador, un agente puede escribir specs de Playwright o Cypress a partir de un flujo de usuario que describas. Los selectores son el punto débil: pide selectores por rol o por etiqueta (getByRole('button', { name: 'Pay' })) en lugar de rutas CSS que se rompen con el próximo rediseño. El artículo sobre testing en el navegador con agentes de IA explica cómo dejar que un agente maneje un navegador real para comprobar su propio trabajo.

Tests de caracterización para código que aún no entiendes

A veces no hay especificación. El código es viejo, quien lo escribió ya no está y nadie sabe exactamente qué hace. Aquí el objetivo se invierte: quieres tests que registren lo que el código hace hoy, bugs incluidos, para darte cuenta cuando eso cambie.

Se llaman tests de caracterización, y son uno de los casos en los que dejar que el agente lea la implementación es lo correcto. El prompt cambia en consecuencia:

Escribe tests de caracterización para legacy/invoice_export.py. Registra el comportamiento actual, incluido todo lo que parezca un bug; no arregles nada. Cubre cada rama que encuentres. Marca cualquier comportamiento que parezca incorrecto con un comentario # SUSPICIOUS: que explique por qué.

Las notas SUSPICIOUS se convierten en tu lista de bugs para más adelante. Aquí valen los tests de tipo snapshot o approval, siempre que sean lo bastante pequeños como para leerlos. Es el primer paso de cualquier refactor seguro, que la guía para refactorizar código legacy con IA recorre paso a paso.

La cobertura sirve para encontrar huecos, no para poner nota

La cobertura es un buen mapa y una mala nota. Úsala para decirle al agente dónde mirar:

StackComando
Vitestnpx vitest run --coverage
Jestnpx jest --coverage
pytest (con pytest-cov)pytest --cov=myapp --cov-report=term-missing
Gogo test -coverprofile=cover.out ./... y luego go tool cover -func=cover.out

Vitest necesita un proveedor de cobertura instalado, como @vitest/coverage-v8. El informe term-missing de pytest lista los números de línea exactos sin cubrir, muy útil para pegarlos directamente en un prompt.

Un buen ciclo:

  1. Ejecuta la cobertura y elige los módulos que importan: facturación, autenticación, migraciones de datos, no código generado.
  2. Dale al agente las líneas sin cubrir y pregúntale qué comportamiento representan.
  3. Haz que escriba tests para ese comportamiento, con valores esperados sacados de las reglas.
  4. Vuelve a ejecutar la cobertura para confirmar que esas líneas ya se ejercitan.

Lo que no debes hacer es decir «sube la cobertura al 90 %». Un agente con un número como objetivo alcanzará el número, y la forma más barata de hacerlo son tests que ejecutan líneas sin comprobar gran cosa.

Mutation testing: ¿los tests comprueban algo de verdad?

El mutation testing responde a la pregunta que la cobertura no puede responder. Una herramienta hace pequeños cambios en tu código, como cambiar > por >=, sustituir + por - o devolver un valor vacío, y ejecuta tus tests contra cada cambio. Si los tests siguen pasando, el mutante «ha sobrevivido» y has encontrado un punto donde los tests no comprueban de verdad el comportamiento.

Herramientas habituales:

  • JavaScript y TypeScript: StrykerJS. Configúralo con npm init stryker@latest y ejecútalo con npx stryker run.
  • Python: mutmut. Ejecuta mutmut run y luego mutmut browse para revisar los mutantes supervivientes. Las versiones recientes necesitan soporte de fork, así que en Windows se ejecuta bajo WSL.
  • Java: PIT, por ejemplo mvn test-compile org.pitest:pitest-maven:mutationCoverage.
  • Rust: cargo-mutants, que se instala con cargo install cargo-mutants y se ejecuta con cargo mutants.

En un código grande las ejecuciones de mutación son lentas, así que limítalas al módulo en el que estés trabajando. Después pásale los supervivientes al agente:

Estos mutantes sobrevivieron en discount.ts: (lista). Para cada uno, escribe un test que falle con el mutante y pase con el original. No cambies discount.ts.

Es uno de los mejores usos de un agente en testing. La tarea es precisa, la comprobación del éxito es mecánica y el resultado es un test que, de forma demostrable, detecta un tipo real de bug.

Un agente escribe los tests y otro escribe el código

La forma más fiable de impedir que un agente doble los tests es asegurarse de que el agente que escribe el código no pueda tocarlos.

El montaje es sencillo:

  1. Agente de tests. Recibe la especificación y escribe los tests en tests/ (o donde vivan tus tests). No ve ni escribe la implementación. Sus tests deberían fallar al principio, porque la funcionalidad todavía no existe.
  2. Agente implementador. Recibe la misma especificación más los tests que fallan y escribe el código hasta que pasen. No puede editar nada en tests/.
  3. Los desacuerdos vuelven atrás. Si el implementador cree que un test está mal, se lo pregunta al agente de tests, o a ti, en lugar de editarlo.

Es desarrollo guiado por tests repartido entre dos agentes, y también funciona entre proveedores distintos. Hay equipos que prefieren que un modelo escriba los tests y otro distinto implemente, para que no compartan los mismos puntos ciegos.

Ejecutar dos agentes sobre la misma funcionalidad requiere algo de aislamiento para que no se pisen. Un git worktree por agente es la respuesta habitual; consulta git worktrees para agentes en paralelo. Para este reparto en concreto también puedes dejar a los dos en el mismo worktree, ya que escriben en carpetas distintas.

Cómo revisar tests escritos por IA

Lee los tests al menos con tanto cuidado como el código. Una lista corta:

  • ¿Los valores esperados salen de las reglas? Busca literales, no fórmulas copiadas del código.
  • ¿Fallaría este test si el código estuviera mal? Elige un test y rompe el código a mano. Si sigue en verde, el test es decoración.
  • ¿Hay algo importante mockeado? Los mocks en la frontera están bien; los mocks del código bajo prueba, no.
  • ¿Ha cambiado algún test existente? En una rama de funcionalidad, los valores esperados modificados en tests antiguos merecen una pregunta. En un diff, git diff --stat -- tests/ lo muestra de un vistazo.
  • ¿Se entienden los nombres? El nombre de un test que falla debería decirte qué se rompió.
  • ¿Es inestable? Vigila la hora real, los datos aleatorios, las llamadas de red y el orden de los tests. Pide al agente que ejecute los tests nuevos varias veces.

La revisión de código automática también ayuda aquí. Un segundo modelo leyendo el diff suele detectar un test que no afirma nada. El artículo sobre revisión de código con IA explica cómo funciona.

Errores habituales

  • «Escribe tests para este archivo» sin nada más. Obtienes la implementación, contada otra vez.
  • Perseguir una cifra de cobertura. Obtienes líneas ejecutadas, no comportamiento comprobado.
  • Dejar que el implementador edite los tests. El camino más rápido al verde es una aserción cambiada.
  • Actualizar snapshots sin leerlos. -u convierte una regresión en la nueva salida esperada.
  • Añadir tests a código que vas a borrar. Prueba el comportamiento que vas a conservar.
  • Dejar tests inestables. Un agente que ve fallar un test inestable «arreglará» encantado algo que no estaba roto. Lo mismo vale para arreglar fallos de CI con IA.

Cómo hacerlo en AgentVera

AgentVera ejecuta CLIs de agentes de código lado a lado por proyecto, así que montar el agente de tests y el implementador lleva un par de minutos.

  • Dos agentes, una funcionalidad. Abre un agente de tests y un implementador en el mismo proyecto, con la misma CLI o con CLIs distintas, por ejemplo Claude Code para los tests y Codex para el código. Ponlos en un worktree aparte desde el panel de worktrees si quieres sacar el trabajo de tu carpeta principal.
  • La propiedad de archivos mantiene al implementador fuera de los tests. En el diálogo del agente de tests, añade tests/** a sus áreas propias. Mientras se ejecuta, los demás agentes no pueden editar esos archivos. En los agentes de Claude Code la edición se rechaza antes de producirse mediante un hook PreToolUse; Codex y las demás CLI reciben la regla en sus instrucciones. Los intentos rechazados aparecen en la línea de tiempo del equipo.
  • Mensajería entre agentes para los desacuerdos. Cuando el implementador cree que un test está mal, puede escribir directamente al agente de tests y esperar su respuesta, en lugar de editar el test.
  • Verify comprueba el resultado. Escribe en tu mensaje qué significa «terminado», por ejemplo «todos los tests de tests/billing pasan y no ha cambiado ningún archivo de tests». Verify lo convierte en criterios y, cuando termina el turno del agente, un verificador independiente ejecuta los tests y marca cada criterio con una evidencia. El verificador puede leer el repo y ejecutar comandos de tests, pero no puede editar archivos.
  • La revisión de código lee los tests. La revisión de código automática examina los cambios de cada turno con el modelo de Claude que elijas y lista los hallazgos con su gravedad y su file:line, una buena segunda mirada para detectar aserciones débiles.
  • Checkpoints para los turnos malos. Cada turno queda guardado en una instantánea, así que si un agente reescribe media suite de tests en una dirección que no te gusta, deshaces ese turno y lo intentas de nuevo. Consulta cómo deshacer los cambios de Claude Code.

Para terminar

La IA abarata escribir tests, y eso significa que el trabajo pasa de teclear tests a juzgarlos. Dale al agente comportamiento en lugar de código, mantén los valores esperados fuera del alcance de la implementación, usa la cobertura como mapa y el mutation testing como nota, y no dejes que el agente que escribe el código decida también qué significa «pasar». Haz eso y la velocidad es real: una suite de tests que crece deprisa y que sigue fallando cuando algo se rompe.

Sigue leyendo:

Para probar AgentVera, empieza en la página de inicio.

Preguntas frecuentes

¿Puede la IA escribir buenos tests unitarios?

Sí, si la guías. Los agentes son buenos con la preparación, los fixtures, las listas de casos límite y el código repetitivo. Fallan cuando sacan los valores esperados del propio código que prueban, porque entonces el test solo repite la implementación. Dales el comportamiento esperado y revisa los tests con mutation testing.

¿Qué es un test tautológico?

Un test que pasa por construcción: calcula el valor esperado igual que el código, mockea justo lo que debería comprobar o solo verifica que se llamó a una función. Sigue en verde aunque la lógica esté mal.

¿Es el 100 % de cobertura un buen objetivo para tests escritos por IA?

No. La cobertura te dice qué líneas se ejecutaron, no si se comprobó algo. Úsala para encontrar zonas sin probar y luego juzga los tests con mutation testing o con una revisión cuidadosa.

¿Debe el mismo agente escribir los tests y el código?

Para cambios pequeños no pasa nada. Para lógica importante conviene separar: un agente escribe los tests a partir de la especificación, otro implementa, y el implementador no puede cambiar los tests. Así se corta la trampa más fácil: retocar el test hasta que pase.

¿Qué agentes puedo usar para esto en AgentVera?

Cualquier agente de CLI que ejecute AgentVera, como Claude Code, Codex o Gemini CLI. La propiedad de archivos bloquea las ediciones de antemano en los agentes de Claude Code; Codex y las demás CLI reciben las reglas de propiedad en sus instrucciones.

Reúne a tus agentes en un mismo escritorio.

Descarga AgentVera gratis; tus CLI instaladas ya están listas.

Más artículos