VOLVER

La salida de tus tests está quemando tokens: domando reporters detallados para agentes de IA

5 min de lectura

Los ejecutores de test como Jest y Vitest vienen con reporters diseñados para humanos que miran terminals. Cada archivo obtiene una línea:

PASS src/components/Button.test.tsx
PASS src/components/Card.test.tsx
PASS src/components/Dialog.test.tsx
PASS src/components/Dropdown.test.tsx
PASS src/utils/format.test.ts
PASS src/utils/date.test.ts
... (200 more lines)
FAIL src/components/Nav.test.tsx
● Nav > renders active state
Expected: "active"
Received: "inactive"

Para un desarrollador en su terminal, ver pasar el texto verde tranquiliza. Pero los tests ahora se ejecutan en otros dos contextos donde esa salida cuesta más de lo que ayuda:

  • CI — nadie lee el log a menos que algo falle. Una build roja te obliga a desplazarte por cientos de líneas "PASS" para encontrar el fallo.
  • Agentes de IA leen cada línea de esa salida. Cada línea "PASS" consume tokens, llenando la ventana de contexto con resultados de test exitosos en lugar del problema real.

Nos topamos con esto en Buffer. Una ejecución de test de 215 suites produjo ~3.500 tokens de salida, casi todas líneas "PASS". Nuestro agente de IA gastó más tokens leyendo resultados de test que escribiendo código. Intentamos añadir --reporter=dot a nuestras instrucciones de CLAUDE.md, pero el agente no siempre lo usaba. El flag era una sugerencia; necesitábamos una garantía.

Detecta el Environment, Elige el Reporter

La solución: detecta el environment en tu configuración de test y cambia de reporters automáticamente. No se requieren instrucciones para el agente, ni flags que recordar.

Claude Code establece CLAUDECODE=1 en cada shell que genera. Los proveedores de CI — GitHub Actions, GitLab CI, CircleCI, Travis CI y Jenkins — todos establecen CI=true. Tu configuración lee estas variables y elige el reporter correcto — determinista independientemente de cómo el agente invoque el comando de test.

Esto es lo que pusimos en producción en Buffer. CI y Claude Code tienen cada uno su propia configuración de reporter; el desarrollo local mantiene el default.

Para Jest, añade la lógica a jest.config.ts. El summary reporter imprime un recuento final más detalles completos de cualquier fallo, sin salida por archivo. El summaryThreshold por defecto de Jest es 20, lo que significa que solo imprime detalles de fallos cuando más de 20 tests fallan. Configúralo a 0 para que cada fallo se imprima completo. En CI, puedes combinarlo con un reporter personalizado que recopile fallos para comentarios en PR de GitHub:

const isCI = process.env.CI === "true";
const isClaude = process.env.CLAUDECODE === "1";
function getReporters() {
if (isCI) {
return [["summary", { summaryThreshold: 0 }], "jest-ci-reporter"];
}
if (isClaude) {
return [["summary", { summaryThreshold: 0 }]];
}
return ["default"];
}
export default {
reporters: getReporters(),
// ... rest of your config
};

Para Vitest, añádelo a vitest.config.ts. El dot reporter comprime cada archivo en un solo carácter — un punto para aprobado, una x para fallo:

import { defineConfig } from "vitest/config";
const isCI = process.env.CI === "true";
const isClaude = process.env.CLAUDECODE === "1";
function getReporters() {
if (isCI) {
return ["dot", "ci-reporter"];
}
if (isClaude) {
return ["dot"];
}
return ["default"];
}
export default defineConfig({
test: {
reporters: getReporters(),
// ... rest of your config
},
});

Ambos frameworks también aceptan flags de reporter en la línea de comandos (--reporters para Jest, --reporter para Vitest). Pero confiar en que un agente de IA pase el flag correcto es probabilístico — el agente puede olvidar o ejecutar un script de test diferente que lo omita. Las variables de environment lo hacen determinista.

La matriz resultante:

EnvironmentJest ReporterVitest Reporter
Desarrollo localdefaultdefault
CIsummary + CI reporterdot + CI reporter
Claude Codesummarydot

Lo que ve el Agente

Antes (reporter por defecto, ~250 líneas):

PASS src/components/Button.test.tsx (3 suites, 12 tests)
PASS src/components/Card.test.tsx (2 suites, 8 tests)
... (200+ more PASS lines)
FAIL src/components/Nav.test.tsx
● Nav > renders active state
expect(received).toBe(expected)
Expected: "active"
Received: "inactive"
Test Suites: 1 failed, 214 passed, 215 total
Tests: 1 failed, 847 passed, 848 total

Después (summary reporter, ~10 líneas):

FAIL src/components/Nav.test.tsx
● Nav > renders active state
expect(received).toBe(expected)
Expected: "active"
Received: "inactive"
Test Suites: 1 failed, 214 passed, 215 total
Tests: 1 failed, 847 passed, 848 total

Mismos detalles de fallo, 96% menos de salida.

Cuando todos los tests pasan, la brecha se ensancha aún más. El reporter por defecto imprime cada nombre de archivo — 215 líneas. El summary reporter imprime dos:

Test Suites: 215 passed, 215 total
Tests: 848 passed, 848 total

Compromisos

Pierdes feedback de progreso. El summary reporter permanece en silencio hasta que la suite termina. Para suites de larga duración, el agente no ve nada hasta la finalización. En la práctica esto no ha importado — los agentes de IA no necesitan tranquilidad de que el proceso se está ejecutando.

Hacer debug de fallos intermitentes se vuelve más difícil. El tiempo por archivo del reporter por defecto ayuda a identificar tests lentos o inestables. Usa el verbose reporter cuando investigues inestabilidad.

La misma solución aplica a Linters y Build Logs

Los test reporters son una interfaz entre tus herramientas y lo que sea que lea la salida. Los linters y verificadores de tipos tienen el mismo problema. Donde sea que una herramienta produzca salida detallada que un agente de IA consuma, puedes detectar el environment y cambiar a un formato compacto. Comprueba tu configuración de test — si process.env.CI está establecido y sigues usando el reporter por defecto, estás pagando por salida que nadie lee.