TORNAR

La sortida dels teus tests està cremant tokens: Domant reporters verbosos per a agents d'AI

5 min de lectura

Els test runners com Jest i Vitest venen amb reporters dissenyats per a humans mirant terminals. Cada fitxer té una línia:

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"

Per a un desenvolupador a la seva terminal, veure passar el text verd tranquil·litza. Però els tests ara s'executen en dos altres contexts on aquesta sortida costa més del que ajuda:

  • CI — ningú llegeix el log a menys que alguna cosa falli. Una build vermella t'obliga a fer scroll més enllà de centenars de línies "PASS" per trobar la fallada.
  • Agents d'AI llegeixen cada línia d'aquesta sortida. Cada línia "PASS" consumeix tokens, omplint la finestra de context amb sortida de test exitosa en lloc del problema real.

Ens vam trobar amb això a Buffer. Una execució de test de 215 suites va produir ~3.500 tokens de sortida, gairebé tots línies "PASS". El nostre agent d'AI va gastar més tokens llegint resultats de test que escrivint codi. Vam provar d'afegir --reporter=dot a les nostres instruccions de CLAUDE.md, però l'agent no sempre l'utilitzava. El flag era un suggeriment; necessitàvem una garantia.

Detecta l'Environment, Tria el Reporter

El fix: detecta l'environment a la teva configuració de test i canvia els reporters automàticament. No calen instruccions per a l'agent, ni flags per recordar.

Claude Code estableix CLAUDECODE=1 a cada shell que genera. Els proveïdors de CI — GitHub Actions, GitLab CI, CircleCI, Travis CI i Jenkins — tots estableixen CI=true. La teva configuració llegeix aquestes variables i tria el reporter correcte — determinista independentment de com l'agent invoqui la comanda de test.

Això és el que vam posar en producció a Buffer. CI i Claude Code tenen cadascun la seva pròpia configuració de reporter; el desenvolupament local manté el default.

Per a Jest, afegeix la lògica a jest.config.ts. El summary reporter imprimeix un recompte final més detalls complets per a qualsevol fallada, sense sortida per fitxer. El summaryThreshold per defecte de Jest és 20, és a dir, només imprimeix detalls de fallades quan més de 20 tests fallen. Configura'l a 0 perquè cada fallada s'imprimeixi completa. A CI, pots combinar-lo amb un reporter personalitzat que recopili fallades per a comentaris de PR a 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
};

Per a Vitest, afegeix-ho a vitest.config.ts. El dot reporter comprimeix cada fitxer a un sol caràcter — un punt si passa, una x si falla:

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
},
});

Ambdós frameworks també accepten flags de reporter a la línia de comandes (--reporters per a Jest, --reporter per a Vitest). Però confiar que un agent d'AI passi el flag correcte és probabilístic — l'agent pot oblidar-se'n o executar un script de test diferent que l'ometi. Les variables d'environment ho fan determinista.

La matriu resultant:

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

Què veu l'Agent

Abans (reporter per defecte, ~250 línies):

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

Després (reporter summary, ~10 línies):

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

Els mateixos detalls de fallada, 96% menys de sortida.

Quan tots els tests passen, la bretxa s'eixampla encara més. El reporter per defecte imprimeix cada nom de fitxer — 215 línies. El reporter summary n'imprimeix dues:

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

Compromisos

Perds el feedback de progrés. El reporter summary es manté en silenci fins que la suite acaba. Per a suites de llarga durada, l'agent no veu res fins a la finalització. A la pràctica això no ha importat — els agents d'AI no necessiten reassegurança que el procés està funcionant.

Fer debug de fallades intermitents és més difícil. El cronometratge per fitxer del reporter per defecte ajuda a identificar tests lents o inestables. Utilitza el reporter verbose quan investiguis la inestabilitat.

El mateix Fix s'aplica a Linters i Logs de Build

Els reporters de test són una interface entre les teves eines i el que sigui que llegeixi la sortida. Els linters i els type checkers tenen el mateix problema. A tot arreu on una eina produeixi sortida verbosa que un agent d'AI consumeixi, pots detectar l'environment i canviar a un format compacte. Comprova la teva configuració de test — si process.env.CI està establert i encara utilitzes el reporter per defecte, estàs pagant per sortida que ningú llegeix.