La IA programa rápido pero rompe cosas: estrategia de tests

La IA genera código en segundos pero no conoce tus dependencias. La estrategia de tests que funciona con agentes de programación.

Colaboradores: Ivan Garcia Villar

Abres el grifo en la cocina y se rompe una ventana en el sótano. No ves la conexión hasta que ya es tarde porque no sabías que había una tubería compartida. Usar IA para programar se parece a esto: el agente añade una feature en 30 segundos, tú haces merge, y tres días después descubres que algo completamente distinto dejó de funcionar. La IA no rompió nada a propósito. Es que no sabía que la tubería existía. Los tests han pasado de ser una buena práctica a ser la única red de seguridad real cuando programas con agentes.

Por qué los agentes IA necesitan tests más que los developers humanos

Un developer senior que lleva ocho meses en un proyecto sabe, sin mirar el código, que tocar la lógica de precios va a afectar al módulo de facturación y al cálculo de impuestos. Ese conocimiento no está en ningún fichero. Está en su cabeza, acumulado con cada bug que ha corregido y cada reunión con el equipo de negocio.

La IA no tiene eso. Cada vez que un agente recibe una tarea, empieza con el contexto que tú le das en ese momento. No recuerda la conversación de la semana pasada ni el bug de producción de hace tres meses. Si no le dices explícitamente que calculateTotal() llama a applyTaxRules(), no lo sabe.

El problema se amplifica con los frameworks que funcionan por convención o “magia implícita”. En Rails, Django o Spring, una gran parte del comportamiento emerge de configuración, anotaciones y convenciones de nombres. Un agente puede modificar un modelo de datos sin entender que ese cambio dispara callbacks, afecta validaciones y rompe serializers. Lo que un developer con experiencia en el framework anticipa instintivamente, el agente no puede deducirlo solo del código que ve.

Los tests son, literalmente, la documentación ejecutable de esas dependencias ocultas. Sin ellos, el agente trabaja a ciegas en un sistema que parcialmente no entiende.

El argumento económico ya no existe

Durante años, el argumento más común contra los tests era el tiempo. Escribir una suite completa para un módulo nuevo podía llevar más tiempo que escribir el módulo en sí. En equipos pequeños, con plazos ajustados, esa ecuación perdía a los tests.

Ese argumento se ha evaporado. Un agente bien instruido genera tests unitarios, de integración y de contrato en minutos. El coste de escribir tests ha caído hasta casi cero. La excusa económica que justificaba no testear ya no es válida.

Pero entonces surge la pregunta incómoda: si los tests son tan baratos de generar, ¿por qué tantos equipos que usan IA siguen sin tener buenas suites de tests? La respuesta no está en el coste. Está en cómo se generan.

El problema de la contaminación: por qué la IA testea mal su propio código

Este es el punto que más se pasa por alto, y el más importante de entender.

Cuando terminas de escribir una función y le pides a la IA que escriba los tests, está contaminada por su propia implementación. El agente acaba de tomar decisiones de diseño. Sabe exactamente cómo funciona internamente el código que acaba de escribir. Cuando genera los tests, tiende a validar que el código hace lo que el agente cree que debe hacer, no lo que los requisitos originales exigen. Prueba los caminos felices que ya contempló. Evita los edge cases que no consideró en la implementación porque, literalmente, no están en su contexto mental en ese momento.

El resultado es una suite de tests que siempre pasa. No porque el código sea correcto, sino porque los tests están escritos para validar la implementación, no los requisitos.

Hay una diferencia fundamental entre estas dos preguntas:

  • “¿Hace el código lo que el código dice que hace?”
  • “¿Hace el código lo que el sistema necesita que haga?”

Un agente que testea su propio código solo puede responder a la primera. Para responder a la segunda, necesita un contexto diferente. Eso es lo que define si tu suite de tests es una red de seguridad real o una ilusión de cobertura.

Cómo diseñar tests que funcionen con agentes IA

La solución es separar los contextos de generación y de testing. Nunca dejes que el mismo agente, en la misma sesión, genere el código y luego lo testee.

El patrón que funciona es TDD para agentes: primero escribe las specs de requisitos (o deja que un agente especializado las genere a partir de los criterios de aceptación), luego usa esas specs como contexto de entrada para el agente de implementación. El agente de implementación trabaja para pasar tests que ya existen, no para escribir tests que validen lo que ya escribió.

// Pseudocódigo — adapta a tu SDK (Anthropic, LangChain, Vercel AI, etc.)
async function tddAgentWorkflow(spec: string, maxIterations = 5): Promise<string> {
  // Los tests se generan UNA sola vez desde la spec, antes de cualquier implementación
  // El agente de tests solo ve la especificación, no ningún código existente
  const tests = await testAgent.generate({ spec });

  let lastFailures = "";

  for (let i = 0; i < maxIterations; i++) {
    const context = lastFailures
      ? spec + "\n\nFailing tests:\n" + lastFailures
      : spec;

    // El agente de implementación ve la spec + los tests que deben pasar
    // No al revés
    const implementation = await implAgent.generate({ spec: context, tests });

    // Nota: en producción, la ejecución debe ocurrir en un entorno aislado
    // (contenedor sin credenciales, sin acceso a red por defecto)
    const result = await runner.execute(tests, implementation);

    if (result.passed) return implementation;
    lastFailures = result.failures;
  }

  throw new Error(`No se alcanzó implementación válida en ${maxIterations} iteraciones`);
}

Cuando necesitas añadir tests a código que ya existe, usa un subagente con contexto deliberadamente acotado. El agente de testing recibe la especificación de requisitos y la interfaz pública (tipos, firmas), pero no la implementación interna.

// Pseudocódigo — adapta a tu SDK (Anthropic, LangChain, Vercel AI, etc.)
// Nota: separar agentes duplica las llamadas a API y la latencia respecto a un agente único
const testingAgent = new Agent({
  systemPrompt: `Eres un especialista en testing. Solo tienes acceso a:
1. La especificación de requisitos
2. La interfaz pública: tipos y firmas de funciones, sin implementación

Escribe tests que validen comportamiento observable, no detalles internos.
Los tests deben fallar si el comportamiento cambia, no si cambia la implementación.`,
  tools: [readSpec, readPublicInterface], // sin acceso a readImplementation
});

Esta separación es lo que convierte los tests generados por IA en una red de seguridad real. Los tests ahora pueden detectar errores que el agente de implementación comete precisamente porque el agente de testing no sabe cómo está construido el código por dentro.

EnfoqueContaminaciónDetecta regresionesSetup
IA genera código + IA testea (misma sesión)AltaBajaInmediato, pero tests inútiles
TDD: specs → tests → implementaciónNulaAltaRequiere specs previas
IA genera + subagente testea con contexto acotadoBajaMedia-AltaRápido, válido para código existente

Para proyectos nuevos, TDD desde el inicio es el enfoque más robusto. Para código existente sin tests, el subagente con contexto acotado es la opción práctica que no requiere reescribir todo.

Ejecutar tests lo más cerca posible del fallo

Escribir buenos tests es la mitad del trabajo. La otra mitad es asegurarte de que se ejecutan en el momento correcto, que es antes de que el código problemático llegue a ningún sitio.

Los git hooks de pre-commit son el mecanismo más efectivo. Cada vez que el agente hace commit, los tests corren automáticamente. No como paso opcional, no como “acuérdate de ejecutarlos después”: como condición necesaria para que el commit exista.

#!/bin/bash
# .git/hooks/pre-commit
# Ejecutar solo los tests relevantes para los ficheros modificados

STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|tsx)$' | grep -Ev '\.(test|spec)\.')

if [ -z "$STAGED_FILES" ]; then
  exit 0
fi

# --findRelatedTests maneja layouts de directorio correctamente (__tests__/, .spec.ts, etc.)
npx jest --findRelatedTests $STAGED_FILES --passWithNoTests

Ejecutar la suite completa en cada commit es lento y no escala. La selección inteligente de tests, basada en los ficheros modificados en ese commit, mantiene el feedback rápido a coste de cobertura parcial — CI ejecuta la suite más amplia.

Los hooks aceleran el feedback local, pero el enforcement real requiere CI obligatorio en branch protection: cualquier agente puede usar --no-verify para saltarse los hooks. Para que el hook forme parte del repo y no solo del entorno local de cada developer, usa herramientas versionadas como Husky o Lefthook en lugar de editar .git/hooks/ directamente.

Hay un riesgo específico con los agentes que vale la pena vigilar. Hemos observado que un agente puede hacer stash de cambios no relacionados antes de un commit, o puede dividir cambios relacionados en commits separados para evadir tests que fallan: es un patrón que puede aparecer cuando el agente optimiza para “hacer commit exitoso” en lugar de “pasar los tests”. Los commits atómicos —un propósito único por commit— reducen este riesgo porque hacen que la correspondencia entre código y tests sea directa y auditable.


La IA ha eliminado la excusa para no testear. Pero si no diseñas los tests con la misma intención con la que diseñas el código, solo automatizarás tests inútiles más rápido. La velocidad del agente amplifica tanto la calidad de tu estrategia de testing como los errores en ella.

Errores comunes

Pedirle a la IA que testee lo que acaba de escribir

El error más frecuente y el más costoso. La sesión termina con tests que pasan al 100%, el developer asume que hay cobertura real, y las regresiones aparecen días después en producción. La solución no es revisar los tests manualmente para verificar si son buenos: es cambiar el flujo para que la contaminación de contexto no sea posible.

Tests que verifican implementación, no comportamiento

Un test que verifica que calculateTotal() llama internamente a applyTaxRules() es un test de implementación. Si refactorizas y usas computeTax() en su lugar, el test falla aunque el comportamiento sea idéntico y el sistema funcione perfectamente. Los agentes generan este tipo de tests con frecuencia porque conocen los internos del código. Los tests útiles verifican entradas, salidas y efectos secundarios observables, independientemente de cómo está construido el código por dentro.

La suite que nunca falla

Si tu suite completa de tests pasa siempre, con cada cambio y cada refactor, hay un problema. Una buena suite tiene tests que fallan cuando el comportamiento cambia. Si introduces deliberadamente un bug en una función crítica y no falla nada, los tests no están cubriendo lo que crees. El mutation testing es útil para verificar periódicamente que la suite detecta cambios reales en el comportamiento. Para TypeScript, Stryker es la herramienta estándar; lo razonable es ejecutarlo de forma nightly o sobre los módulos críticos, no en cada commit.

No configurar git hooks desde el principio

Añadir git hooks después de que el agente ha generado cientos de commits es técnicamente posible pero culturalmente difícil. El flow ya está establecido, los atajos ya son costumbre. El momento de configurarlos es al iniciar el proyecto, antes de que el agente genere la primera línea de código productivo. Una vez que el hook forma parte del entorno desde el inicio, el agente lo respeta como parte del contrato de trabajo.

Checklist de implementación

  • El agente de tests no tiene acceso al código de implementación en su contexto
  • Las specs de requisitos están escritas antes de que empiece la implementación
  • El pre-commit hook ejecuta tests de los ficheros modificados en cada commit
  • Los commits son atómicos: un propósito único y claro por commit
  • La suite incluye tests que verifican comportamiento observable, no detalles internos
  • Se ejecuta mutation testing periódicamente para verificar la efectividad de la suite
  • El agente de implementación recibe los tests fallidos como contexto de feedback en el loop de iteración

Un concepto nuevo cada semana

Preguntas Frecuentes

¿No es suficiente con revisar el código que genera la IA antes de hacer merge?

La revisión manual detecta problemas de legibilidad, patrones incorrectos y errores obvios en el código que estás mirando. Lo que no detecta son las interacciones inesperadas entre módulos que solo aparecen en ejecución. Un developer puede revisar código correcto que rompe comportamiento correcto en otro módulo del sistema. Los tests automatizan la verificación de esas interacciones de forma repetible, sin depender de que alguien recuerde qué componentes están relacionados ni de la atención de quien hace la revisión.

¿Cómo evito que la IA escriba tests que siempre pasan?

La causa del problema es la contaminación de contexto: el agente testea su propia implementación porque la conoce. La solución es separar los contextos. Usa un subagente específico para testing que solo recibe la especificación de requisitos y la interfaz pública del código, sin acceso al código de implementación. Si el agente de testing no sabe cómo está construido el código por dentro, no puede sesgar los tests hacia esa implementación concreta.

¿Qué tipo de tests debo priorizar cuando trabajo con agentes IA?

Los tests de comportamiento observable son los más valiosos: entradas, salidas y efectos secundarios medibles. Después, los tests de integración que verifican que los módulos funcionan juntos correctamente, porque es donde los agentes tienen más problemas con dependencias implícitas entre componentes. Los tests que verifican detalles de implementación interna tienen poco valor y mucho coste de mantenimiento cuando el agente refactoriza código.

¿Vale la pena hacer TDD si la IA puede refactorizar el código después?

TDD con agentes funciona mejor que TDD sin agentes, no peor. El agente es excelente ejecutando el ciclo “escribir código que pase tests existentes”, porque los tests son especificaciones concretas y ejecutables que el agente puede verificar directamente. Si la IA refactoriza después, los tests garantizan que el comportamiento no cambia. Sin ellos, cada refactor es una operación de riesgo desconocido donde solo te enteras de los problemas en producción.

¿Cómo configuro un pre-commit hook sin que ralentice demasiado el flujo?

La clave es la selección inteligente de tests: ejecutar solo los tests que cubren los ficheros modificados en ese commit, no la suite completa. Con una estrategia de mapeo fichero-fuente a fichero-test, el hook tarda segundos en lugar de minutos. Si la suite completa tarda más de 30 segundos, configura el hook para tests rápidos (unitarios) en pre-commit y deja los tests lentos de integración para el pipeline de CI.