Lección 10 — Solo las pruebas end-to-end son verificación real

Por: Artiko
harnesstestingend-to-endarquitecturalintci

Lección 10 — Solo las pruebas end-to-end son verificación real

Le pedís al agente que agregue exportación de archivos a una aplicación Electron. Escribe el componente del proceso de render, el preload script y la lógica de la capa de servicio. Las pruebas unitarias de cada componente pasan perfectamente. El agente dice “ya está”. Cuando realmente hacés clic en el botón de exportar: el formato de la ruta es incorrecto, la barra de progreso no se actualiza y exportar archivos grandes causa una fuga de memoria. Cinco defectos en los límites de componentes, y las pruebas unitarias no detectaron ninguno.

Es como un ensayo de coro: cada cuerda suena perfecta cuando canta sola, pero cuando cantan juntos las sopranos van medio tiempo más rápido que los bajos y el acompañamiento está un semitono desfasado. Cada parte es “correcta” por sí sola, pero el conjunto está desafinado.

La pirámide de testing de Google nos dice que una gran cantidad de pruebas unitarias es la base, pero si te quedás ahí vas a perder sistemáticamente los problemas de interacción entre componentes. Para los agentes el problema es más grave: tienden a ejecutar solo las pruebas más rápidas y después declarar la finalización.

Los puntos ciegos de las pruebas unitarias

La filosofía de diseño de las pruebas unitarias es el aislamiento: mockear dependencias y enfocarse solo en la unidad bajo prueba. Esto las hace rápidas y precisas, pero también crea puntos ciegos sistemáticos.

Punto ciegoQué pasaPor qué la unitaria no lo ve
Incompatibilidad de interfacesEl render pasa una ruta relativa; el preload espera absolutaCada uno mockeó al otro y pasó
Errores de propagación de estadoLa migración cambia el esquema; el caché del ORM guarda el viejoCada test crea un entorno mock fresco
Ciclo de vida de recursosFile handles, conexiones y sockets abarcan varios componentesCada test crea y destruye recursos independientes
Dependencia del entornoConfig, latencia de red, servicio no disponibleEn el test todo está mockeado

Las E2E no solo cambian los resultados: cambian el comportamiento

Esto es lo que mucha gente no llega a captar: cuando un agente sabe que su trabajo va a ser sometido a pruebas end-to-end, su comportamiento de codificación cambia.

  1. Considera las interacciones entre componentes. Mientras escribe código, piensa en “cómo se conecta esta interfaz con lo de arriba”, en lugar de enfocarse solo en una función.
  2. Respeta los límites de la arquitectura. En sistemas con restricciones arquitectónicas, las E2E obligan al agente a adherirse a las reglas.
  3. Maneja rutas de error. Las E2E generalmente incluyen escenarios de fallo, obligando al agente a considerar el manejo de excepciones.

Conceptos clave

  • Defectos en los límites de componentes: A y B pasan sus pruebas unitarias, pero su interacción produce comportamiento incorrecto.
  • Gradiente de adecuación de pruebas: defectos detectados por unitarias ≤ integración ≤ end-to-end. Cada capa superior aumenta la capacidad de detección.
  • Reglas de cumplimiento de límites arquitectónicos: convertir reglas de documentos (“el proceso de render no puede acceder al filesystem”) en verificaciones ejecutables y automatizadas.
  • Promoción de retroalimentación de revisiones: convertir comentarios repetidos de code review en pruebas automatizadas. Cada vez que aparece un problema recurrente, se agrega una regla y el harness se fortalece solo.
  • Mensajes de error orientados al agente: los mensajes de fallo no deberían solo decir qué salió mal, sino exactamente cómo solucionarlo.

Cómo hacerlo

0. Primero los límites arquitectónicos, después las E2E

El prerrequisito de las pruebas end-to-end son límites de sistema claros. Si la arquitectura es un plato de fideos, las E2E solo van a probar que “este plato de fideos funciona”.

La experiencia de OpenAI: para bases de código generadas por agentes, las restricciones arquitectónicas deben ser prerrequisitos tempranos establecidos desde el primer día, no algo a considerar cuando el equipo crezca. La razón es simple: los agentes copian los patrones existentes en el repositorio, incluso si esos patrones son desparejos o subóptimos.

OpenAI adoptó una arquitectura de dominio en capas:

flowchart LR
    T["Types"] --> C["Config"]
    C --> R["Repo"]
    R --> S["Service"]
    S --> RT["Runtime"]
    RT --> UI["UI"]
    X["Otro dominio"] -.solo vía Providers.-> S

Las dependencias fluyen estrictamente hacia adelante, y las preocupaciones entre dominios entran a través de interfaces de Providers explícitas. Cualquier otra dependencia está prohibida y se hace cumplir mecánicamente mediante linting personalizado.

Principio clave: hacé cumplir invariantes, no microgestiones la implementación. Por ejemplo, requerir “los datos se parsean en el límite”, pero no dictar qué biblioteca usar.

Fuente: OpenAI — Harness engineering: leveraging Codex in an agent-first world

1. El harness debe incluir una capa end-to-end

## Jerarquía de validación
- Nivel 1: Pruebas unitarias (deben pasar)
- Nivel 2: Pruebas de integración (deben pasar)
- Nivel 3: Pruebas end-to-end (obligatorias cuando hay cambios entre componentes)
- Saltear cualquier nivel requerido = No completado

2. Convertí reglas arquitectónicas en verificaciones ejecutables

# ¿El proceso de render llama directamente a APIs de Node.js?
grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: sin acceso directo a fs en renderer"

3. Diseñá mensajes de error orientados al agente

Los mensajes de fallo deben contener tres elementos: qué salió mal, por qué y cómo solucionarlo.

ERROR: Se encontró un import directo de 'fs' en src/renderer/App.tsx:12
POR QUÉ: El proceso de render no tiene acceso a APIs de Node.js por seguridad
ARREGLO: Mové las operaciones de archivo a src/preload/file-ops.ts
         e invocalas vía window.api.readFile()

4. Establecé un proceso de promoción de retroalimentación

flowchart LR
    A["Aparece un comentario<br/>recurrente en code review"] --> B["Se identifica el patrón<br/>de error del agente"]
    B --> C["Se escribe una verificación<br/>automatizada (lint o test)"]
    C --> D["Se agrega el mensaje<br/>de error con la corrección"]
    D --> E["Se integra en CI<br/>y en el harness"]
    E --> F["El agente se autocorrige<br/>sin intervención"]
    F --> A

Un mes después, tu harness va a ser significativamente más fuerte que al inicio del mes.

Caso del mundo real

Tarea: implementar exportación de archivos en una aplicación Electron. Involucra la UI del proceso de render, el proxy de filesystem del preload script y la transformación de datos de la capa de servicio.

Las pruebas unitarias de los tres componentes pasaron (con todo mockeado) y el agente declaró finalización. Después vinieron las E2E:

DefectoDescripciónUnitariaE2E
Incompatibilidad de interfazFormato de ruta inconsistenteNo detectadoDetectado
Propagación de estadoEl progreso de exportación no vuelve a la UI por IPCNo detectadoDetectado
Fuga de recursosHandles de archivos grandes no liberadosNo detectadoDetectado
PermisosPermisos distintos en el entorno empaquetadoNo detectadoDetectado
Propagación de erroresLas excepciones del servicio no llegaban a la UINo detectadoDetectado

Los 5 defectos fueron detectados por las pruebas end-to-end; las unitarias no detectaron ninguno. El costo fue un aumento del tiempo de prueba de 2 a 15 segundos: completamente aceptable en un flujo de trabajo con agentes.

Ideas clave

  • Las pruebas unitarias son sistemáticamente ciegas a los defectos en los límites de componentes: su diseño de aislamiento es precisamente lo que se los impide.
  • Las E2E no solo detectan defectos: cambian el comportamiento de codificación del agente, haciéndolo enfocarse más en la integración y los límites.
  • Las reglas arquitectónicas deben ser ejecutables, no escritas en un documento esperando ser leídas.
  • Los mensajes de error deben estar diseñados para agentes, incluyendo pasos concretos de corrección.
  • La promoción de retroalimentación hace que el harness se fortalezca solo: cada categoría de defecto capturado se convierte en una línea de defensa permanente.

Ejercicios

  1. Detección de defectos entre componentes: elegí una tarea que involucre al menos tres componentes. Corré primero solo pruebas unitarias, después E2E. Analizá a qué tipo de problema de interacción pertenece cada defecto adicional.
  2. Automatización de reglas arquitectónicas: elegí una restricción de tu proyecto y convertila en una verificación ejecutable con mensaje de error orientado al agente. Integrala en el harness y verificá su efectividad.
  3. Promoción de retroalimentación: encontrá un tipo de comentario recurrente en tu historial de code review y convertilo en verificación automatizada. Compará la frecuencia del problema antes y después.

Lecturas adicionales


Anterior: Lección 09 — Evitá que el agente declare victoria antes de tiempo · Siguiente: Lección 11 — Hacé observable el runtime del agente