Testing e feedback loop: far lavorare meglio gli agenti AI sul codice
Se usi agenti AI per scrivere codice, il problema non è soltanto ottenere una buona implementazione iniziale. Il problema vero è capire quando l'agente sta andando nella direzione giusta e quando invece sta accumulando errori che scoprirai troppo tardi.
Qui entra in gioco il testing. Non come rituale da eseguire a fine giornata, ma come sistema di feedback che accompagna l'agente durante il lavoro.
In pratica: un agente senza feedback lavora per inerzia. Un agente con feedback deterministico lavora per correzioni successive.
Questo articolo mostra come progettare test e verifiche che gli agenti riescono davvero a usare, in modo coerente con i workflow discussi in Prompting e workflow e con l'idea di contratti chiari già emersa in Deep Modules in React/TypeScript.
1. Il problema: l'agente non sa se sta migliorando o peggiorando il sistema
Uno sviluppatore umano ha molte fonti di feedback implicito:
- riconosce un refactor sospetto a colpo d'occhio
- nota subito una convenzione violata
- capisce se una modifica "sembra sbagliata" anche prima di eseguire il codice
Un agente AI no. Ragiona sul contesto che gli hai fornito e sulle verifiche che riesce a osservare.
Se gli chiedi:
Aggiungi la validazione all'endpoint di checkout e mantieni tutto compatibile.senza dargli una forma di verifica, l'agente può:
- introdurre una regressione in un altro endpoint
- cambiare il contratto di ritorno senza accorgersene
- spostare logica dove non dovrebbe stare
- dichiarare il task completato solo perché il codice "sembra plausibile"
Il punto critico è questo: gli agenti non hanno bisogno solo di istruzioni. Hanno bisogno di un oracolo.
Un buon test suite, un typecheck ben configurato e un paio di verifiche mirate svolgono esattamente questa funzione.
Quando lavori con agenti AI, i test non sono solo una rete di sicurezza. Sono il linguaggio con cui il sistema dice all'agente: corretto, incompleto o sbagliato.
2. Che cosa rende utile un feedback per un agente
Non tutte le verifiche sono ugualmente utili. Un feedback efficace per un agente ha quattro proprietà.
Deterministico
Lo stesso input deve produrre lo stesso risultato. Se un test fallisce una volta su tre, per l'agente non è un segnale: è rumore.
Locale
Il messaggio di errore deve essere collegabile con chiarezza alla modifica appena fatta. Se tocchi un parser e falliscono 70 test end-to-end non correlati, il feedback è troppo dispersivo.
Rapido
Se ogni ciclo modifica-verifica richiede minuti, l'agente accumula più cambiamenti prima di fermarsi. A quel punto il costo della correzione sale troppo.
Azionabile
Un errore come:
Something went wrong during application startup.è quasi inutile. Un errore come:
Expected response body.status to be "confirmed"
Received "pending"è invece un segnale chiaro: il contratto si è rotto in un punto preciso.
3. L'ordine corretto delle verifiche
Quando un agente modifica il codice, non tutte le verifiche hanno lo stesso rapporto costo/valore. Conviene usare una sequenza a strati.
Primo strato: typecheck e lint mirato
Il typecheck è spesso il feedback più economico che puoi dare a un agente TypeScript.
pnpm tsc --noEmit
pnpm eslint src/features/checkoutSe il task rompe tipi, import o firme pubbliche, vuoi scoprirlo subito. Questo evita che l'agente costruisca altre modifiche sopra una base già incoerente.
Secondo strato: test locali del modulo toccato
Subito dopo il typecheck, fai eseguire all'agente i test più vicini alla modifica.
pnpm vitest run src/features/checkout/checkout.service.test.tsQuesto è il punto in cui il feedback è più denso di informazione e meno costoso da interpretare.
Terzo strato: integrazione mirata
Se la modifica attraversa più moduli, servono test che verifichino il comportamento di confine.
pnpm vitest run src/features/checkout/checkout.integration.test.tsQuarto strato: e2e o smoke test selettivi
Gli end-to-end restano utili, ma non devono essere il primo feedback. Sono costosi, lenti e spesso troppo rumorosi per guidare l'iterazione iniziale dell'agente.
La regola pratica è semplice:
- prima i segnali rapidi
- poi i segnali locali
- solo dopo i segnali ampi
4. I test che aiutano davvero gli agenti
Test di contratto
Sono i più importanti quando vuoi che un agente modifichi un modulo senza rompere il resto della codebase.
Se una funzione pubblica promette una certa forma di input/output, il test deve codificare quel contratto.
import { describe, expect, it } from "vitest"
import { createCheckout } from "./checkout.service"
describe("createCheckout", () => {
it("restituisce un checkout confermato con totale e currency", async () => {
const result = await createCheckout({
customerId: "cust_123",
items: [{ sku: "book-1", quantity: 2 }],
})
expect(result.status).toBe("confirmed")
expect(result.total.amount).toBeGreaterThan(0)
expect(result.total.currency).toBe("EUR")
})
})Questo tipo di test non dice soltanto se il codice funziona. Dice all'agente quali parti del comportamento non sono negoziabili.
Test di integrazione tra moduli
Sono essenziali quando il task attraversa repository, service, route handler o adapter esterni. Un agente tende a ottimizzare il file che sta guardando; il test di integrazione ricorda che il comportamento reale attraversa confini.
Test sui casi limite
Gli agenti sono forti sul caso felice. Cadono molto più spesso su input vuoti, stati intermedi, errori di rete, timeout e dati parziali.
Se questi casi non sono catturati nei test, l'agente spesso li tratta come dettagli secondari.
it("fallisce con errore di dominio se il carrello è vuoto", async () => {
await expect(
createCheckout({ customerId: "cust_123", items: [] })
).rejects.toThrow("Cart is empty")
})Test di regressione nati da bug reali
Ogni volta che l'agente o un umano introduce un bug, il modo migliore per evitare il ritorno dello stesso problema non è scrivere una nota nel PR. È aggiungere un test specifico.
Nel tempo, questi test costruiscono una memoria esterna che l'agente può consultare a ogni esecuzione.
5. I test meno utili per guidare l'agente
Non significa che siano inutili in assoluto. Significa che sono meno efficaci come meccanismo di feedback durante la generazione iterativa del codice.
Snapshot enormi e opachi
Se un test fallisce con cento righe di snapshot modificate, l'agente ha un segnale poco leggibile. Potrà forse aggiornare lo snapshot, ma non avrà capito davvero il comportamento rotto.
End-to-end troppo larghi come primo controllo
Se per cambiare una funzione devi lanciare tutta l'applicazione e simulare un flusso completo, stai dando all'agente un feedback costoso e poco locale.
Test che dipendono da clock, rete o dati instabili
Flakiness e agenti AI non vanno d'accordo. Un agente non ha il buon senso contestuale necessario per capire quando ignorare un falso positivo.
Messaggi di assert generici
Una assertion vaga è una richiesta di interpretazione. L'agente rende meglio quando può associare fallimento e correzione in modo quasi meccanico.
6. Test come contratto, non come archivio di implementation details
Uno degli errori più comuni è scrivere test che inchiodano la struttura interna invece del comportamento.
Questo danneggia anche il lavoro degli agenti. Se il test verifica dettagli irrilevanti dell'implementazione, ogni refactor legittimo sembra una regressione.
Prendi questo esempio:
it("chiama prima validateCart e poi persistOrder", async () => {
const validateCart = vi.fn()
const persistOrder = vi.fn()
await createCheckout(deps)(input)
expect(validateCart).toHaveBeenCalledBefore(persistOrder)
})Questo test può essere utile in casi rari, ma spesso blocca refactor innocui.
Molto meglio esprimere il contratto osservabile:
it("persiste un ordine solo se il carrello è valido", async () => {
const persistOrder = vi.fn()
await expect(
createCheckout({ persistOrder })({ customerId: "cust_123", items: [] })
).rejects.toThrow("Cart is empty")
expect(persistOrder).not.toHaveBeenCalled()
})Il principio è lo stesso che vale per i moduli profondi: interfacce piccole, comportamento ben difeso. Quando i test proteggono il contratto e non i dettagli accidentali, l'agente ha più spazio per migliorare il codice senza rompere l'architettura.
7. Come strutturare un workflow agent-friendly
Il modo più semplice per peggiorare la qualità dell'output è chiedere a un agente di fare troppe cose prima di verificare qualcosa.
Meglio un workflow esplicito come questo:
Task: aggiungi il supporto ai coupon nel checkout.
Procedi in questo ordine:
1. Elenca i file da modificare.
2. Implementa il dominio nel service.
3. Esegui typecheck.
4. Esegui i test del service.
5. Aggiorna route handler e adapter.
6. Esegui i test di integrazione del checkout.
7. Riassumi cosa hai cambiato e quali test hai eseguito.
Se un controllo fallisce, fermati, spiega il fallimento e correggilo prima di proseguire.Questa sequenza fa due cose importanti:
- limita la dimensione del contesto attivo
- impedisce all'agente di dichiarare vittoria prima di aver verificato il comportamento
Una variante molto efficace è chiedere output strutturati dopo ogni fase:
Rispondi dopo ogni step con:
- file toccati
- comando eseguito
- esito
- eventuale prossimo bloccoNon è burocrazia. È compressione del contesto. Ogni checkpoint produce un artefatto leggibile sia da te sia da un eventuale agente successivo.
8. Il typecheck è già un test, se lo tratti come tale
Molti team separano mentalmente i test dal type system. Con gli agenti è una distinzione poco utile.
Per una codebase TypeScript ben mantenuta, il typecheck è uno dei migliori feedback loop disponibili perché:
- è veloce
- è preciso
- punisce subito le interfacce incoerenti
- costringe l'agente a rispettare i contratti pubblici
Se vuoi che gli agenti lavorino bene, investi in tipi espliciti e firme stabili. Un progetto con tipi vaghi o any diffusi produce agenti più "creativi" proprio dove non dovrebbero esserlo.
type CheckoutResult =
| { status: "confirmed"; total: { amount: number; currency: "EUR" } }
| { status: "rejected"; reason: "out_of_stock" | "invalid_coupon" }Con un contratto del genere, l'agente ha meno spazio per inventare shape intermedie o campi inconsistenti.
9. Errori comuni quando si delega il testing agli agenti
Errore 1: chiedere implementazione e test in un unico salto troppo grande
"Implementa la feature e aggiungi tutti i test necessari" sembra efficiente, ma su task medi o grandi produce spesso codice e test che si giustificano a vicenda invece di verificarsi a vicenda.
Meglio spezzare:
- prima definire il comportamento atteso
- poi scrivere o aggiornare i test chiave
- infine implementare o rifinire il codice
Errore 2: accettare test verdi ma deboli
Un agente può far passare i test anche rendendoli meno espressivi, allentando le assertion o coprendo solo il happy path. Verde non significa forte.
Errore 3: non controllare la qualità dei messaggi di fallimento
Se il test fallisce in modo opaco, l'agente compirà tentativi più casuali. Vale la pena curare anche il testo degli errori più importanti.
Errore 4: lasciare zone critiche senza verifiche locali
Se auth, checkout, billing o sincronizzazione dati non hanno test rapidi e vicini al dominio, l'agente dovrà orientarsi con troppo poco segnale.
10. Una checklist pratica per rendere i test utili agli agenti
Prima di affidare un task a un agente, chiediti:
- esiste un typecheck affidabile e rapido?
- esistono test locali del modulo da toccare?
- i test descrivono comportamento o dettagli interni?
- i casi limite importanti sono coperti?
- i messaggi di errore sono leggibili e specifici?
- i test sono abbastanza veloci da poter essere eseguiti più volte nello stesso task?
- il workflow impone una verifica tra una fase e la successiva?
Se la risposta è no a più di un punto, il problema non è l'agente: è il sistema di feedback che gli stai dando.
Conclusione
L'idea centrale è semplice: gli agenti AI non migliorano per intuizione, migliorano per iterazione guidata da segnali affidabili.
Per questo, in una codebase agent-friendly, i test non arrivano alla fine. Sono parte del prompt operativo. Sono parte del workflow. Sono parte dell'architettura.
Se vuoi aumentare l'autonomia di un agente senza perdere controllo, non partire da prompt più lunghi. Parti da feedback migliori:
- typecheck pulito
- test di contratto chiari
- integrazioni mirate
- checkpoint frequenti
Il risultato non è solo codice più corretto. È un processo in cui agente e sviluppatore riescono finalmente a lavorare sulla stessa mappa.