Salta al contenuto principale

TDD enforcement skill: RED-GREEN-REFACTOR automatico in Claude Code

Cluster 4 della serie Claude Code Workflow. La skill tdd-guide forza RED-GREEN-REFACTOR su business logic nuova: scrivi il test prima, lo vedi fallire, scrivi il minimo per passarlo, refactori. Quando funziona, quando NON conviene, anti-pattern reali.

Cosa ne pensi di questo articolo?

TDD enforcement skill: RED-GREEN-REFACTOR automatico in Claude Code
Claude Code WorkflowFoto:PixabayPexels

In Breve

Cluster 4 della serie Claude Code Workflow. La skill tdd-guide forza RED-GREEN-REFACTOR su business logic nuova: scrivi il test prima, lo vedi fallire, scrivi il minimo per passarlo, refactori. Quando funziona, quando NON conviene, anti-pattern reali.

Risposta diretta

La skill impone l'ordine corretto del TDD, facendo fallire il test prima di scrivere l'implementazione, e verifica la sua esecuzione reale, non solo simulata, per garantire che i test siano rigorosi e non si limitino a documentare il comportamento attuale del codice.

Test-Driven Development scritto in autonomia da un LLM è sospetto: l'output sembra TDD ma in realtà è "write code, then write tests around it". La skill tdd-guide di Superpowers risolve esattamente questo: forza l'ordine corretto (test prima dell'implementazione) bloccando la generazione di codice finché non esiste un test che fallisce.

Disclaimer di indipendenza: non sono affiliato a obra né ad Anthropic. Uso tdd-guide da circa un anno su tutta la business logic nuova del mio portfolio. Sulle parti dove la skill non ha senso (UI, glue code) la disabilito esplicitamente nel CLAUDE.md di progetto.

Il problema con il TDD generato da LLM "a istinto"

Senza la skill, una sessione tipica produce:

  1. "Scrivi una funzione che fa X"
  2. Modello scrive l'implementazione.
  3. "Ora scrivi i test."
  4. Modello scrive test che passano già, perché osservano l'implementazione esistente.

Risultato: test che documentano il comportamento corrente del codice, non che guidano il design. Coverage cresce, fiducia non aumenta. Se l'implementazione era sbagliata, i test sono sbagliati per simmetria. È il modo più diffuso di scrivere "TDD" che non è TDD.

La skill tdd-guide impone l'ordine corretto: il test esiste e fallisce prima che l'implementazione venga scritta. La verifica avviene eseguendo davvero il test (non simulando).

Il ciclo RED-GREEN-REFACTOR applicato

Ogni feature passa attraverso un ciclo (o più, per edge case):

  1. RED: scrivi il test che descrive il comportamento atteso. Eseguilo. Deve fallire (perché l'implementazione non esiste o ha logica sbagliata). Se passa, non è un test valido - sta osservando codice già funzionante.
  2. GREEN: scrivi il minimo codice che fa passare il test. Resisti alla tentazione di "scrivere quello che servirà domani". Solo quello che il test richiede oggi.
  3. REFACTOR: con i test che passano come rete di sicurezza, riorganizza il codice. Estrai funzioni, semplifica nomi, deduplica. Re-esegui i test dopo ogni modifica.

La skill tdd-guide formalizza questo loop come tre gate espliciti. Ogni gate deve essere superato prima di passare al successivo. Niente "GREEN senza RED prima"; niente "REFACTOR senza GREEN stabile".

Esempio illustrativo: validatore IBAN per il dev-tool

Sul portfolio ho un dev-tool generatore IBAN per 50+ paesi. Quando ho aggiunto la validazione MOD-97 (ISO 7064), ho usato tdd-guide. Sintesi della sessione (riassunto, non literal transcript):

RED 1 (happy path)
  Test: validateIban("IT60 X054 2811 1010 0000 0123 456") -> true
  Esecuzione: fallisce (funzione non esiste)
  Esecuzione dopo creazione signature: fallisce (return false default)

GREEN 1
  Implementazione minima: parse + MOD-97 check
  Test passa

RED 2 (edge case: spazi multipli)
  Test: validateIban("IT60  X054 2811...") -> true (gestione spazi)
  Esecuzione: fallisce (regex strict)

GREEN 2
  Aggiunta normalize-whitespace prima della validation
  Entrambi i test passano

RED 3 (edge case: lowercase)
  Test: validateIban("it60 x054...") -> true (case-insensitive)
  Esecuzione: fallisce (case-sensitive lookup table country)

GREEN 3
  toUpperCase() su country code + intero IBAN
  Tutti i test passano

REFACTOR
  Estrai normalizeIban() come helper privato
  Estrai countryCheckDigitTable come const top-of-file
  Re-esegui suite: green

Risultato: una funzione di ~40 righe + 12 test. Coverage 100% sulla funzione. Refactor finale ha estratto due helper riusabili.

Quando la skill non conviene

tdd-guide ha overhead. Tre casi in cui è meglio non invocarla:

  1. UI puro: componenti Angular/React che renderizzano dati senza logica interna. Snapshot test o visual regression test sono sufficienti; TDD strutturato è overkill.
  2. Glue code: codice che orchestra chiamate fra librerie esistenti (es. fetch + format + display). Il valore del test è basso rispetto al costo di scriverlo.
  3. CRUD semplice: repository con metodi find/save/delete delegati a un ORM. Test integration testano l'integrazione (DB reale), unit test sull'astrazione sono ridondanti.

Sul mio portfolio uso una rule esplicita nel CLAUDE.md di progetto: "TDD per business logic (calcolatori fiscali, validazioni, transformations). Standard execute per UI, glue, CRUD." La skill viene invocata in automatico solo per pattern del primo gruppo.

Anti-pattern: "test theatre"

Il test theatre è quando scrivi test che sembrano rigorosi ma non testano nulla di significativo. Pattern tipici:

  • Test che testano l'implementazione: invece di verificare comportamento osservabile, verificano l'invocazione di un metodo specifico. "Aspettativa: userService.create chiamato una volta." Refactor che cambia il metodo rompe il test, anche se il comportamento è invariato.
  • Test con mock totale: tutto è mockato, niente è esercitato davvero. Verifica passa, in produzione esplode.
  • Test triviali "per coverage": expect(true).toBe(true) wrappato in un test descrittivo. Il file ha test, ma non protegge da nulla.

La skill tdd-guide evita questi anti-pattern istruendo a partire dal comportamento osservabile esterno (input/output della funzione, response dell'endpoint), non dall'implementazione interna. Il RED del primo ciclo è quasi sempre un assert su output, non su side effect.

Pattern: TDD su API endpoint

Su un endpoint REST tipico, il ciclo TDD si applica così:

  1. RED 1 (happy path): test integration che chiama l'endpoint con payload valido, attende 200 + response body specifico.
  2. GREEN 1: implementa controller + use case minimo che produca quel response.
  3. RED 2 (validation 400): test integration con payload invalido, attende 400 + error message specifico.
  4. GREEN 2: aggiungi validation + error handling.
  5. RED 3 (auth 401): test con JWT mancante o scaduto, attende 401.
  6. GREEN 3: aggiungi guard auth.
  7. REFACTOR: estrai mapper DTO, riorganizza error handling.

Cinque-otto cicli RED-GREEN per endpoint medium complexity. Tempo: 30-60 minuti con la skill. Vantaggio: endpoint live coverage da subito, no "test backfill" futuro.

Integrazione con CI threshold

Sul portfolio uso threshold di coverage come gate CI:

  • Backend Spring Boot: JaCoCo, soglia minima 30%, target progressivo 80%.
  • Frontend Angular: Istanbul, soglia 25% (in crescita), target 80%.
  • ML service Python: pytest-cov, soglia 55%, target 80%.

La skill tdd-guide produce naturalmente test che incrementano coverage perché ogni feature nuova arriva con il proprio test set. Sul mio repository, da quando ho adottato la skill consistentemente sui nuovi PR, il coverage backend è cresciuto da 28% a 41% senza nessun "test backfill sprint" dedicato. Per il frontend la crescita è più lenta (architettura più legacy), ma il pattern resta.

Compatibilità con esecuzione asincrona

Codice async (Promise, async/await) è notoriamente più difficile da testare. La skill istruisce a tre pattern specifici:

  1. Async test: async function nel test + await sulla chiamata, mai callback puri.
  2. Timeout esplicito: ogni test async dichiara un timeout sensato (5-30 sec). Test che superano timeout = fail deterministico.
  3. No sleep arbitrari: niente "aspetta 500ms e controlla". Usa polling fino a condition o eventi del framework.

Il body della skill ha una sezione dedicata agli edge case async, che il modello legge solo quando il test richiede pattern asincroni (lazy-load della reference).

Sessione tipica: tempi e costi

  • Feature medium (4-6 cicli RED-GREEN): 30-50 minuti, 20-40k token.
  • Feature complessa (10+ cicli, multipli edge case): 60-120 minuti, 50-100k token.
  • Bug fix con regression test: 15-30 minuti, 10-20k token.

Confronto: senza skill, le stesse feature con test "scritti dopo" consumano simile token totale ma producono test theatre più del 30% delle volte (campione personale, periodo pre-skill).

Checklist invocazione skill TDD

  1. La feature ha input/output definiti e osservabili? (sì = TDD; no = standard execute)
  2. È business logic (calcoli, validazioni, transformations, algoritmi)? (sì = TDD)
  3. È UI puro, glue code, o CRUD semplice? (sì = standard execute)
  4. Esiste già un test framework configurato? Se no, primo step è scaffolding framework (Wave 0).

Variante custom: TDD per data transformation pipeline

Sul portfolio ho una variante custom di tdd-guide specializzata su transformation pipeline (ETL, normalizzazione dataset, parsing complesso). Modificata rispetto alla base per:

  1. Partire sempre da un sample dataset reale (non sintetico).
  2. Test di "shape" (struttura output) prima di test di "value" (correttezza valori).
  3. Snapshot test sui sample tipici, unit test sulle funzioni di transformation.

La variante è efficace su pipeline dove la sfida è la coerenza strutturale, non l'algoritmo (es. parsing XLF i18n, normalizzazione content article frontmatter).

Quando il modello "bara"

Capita raramente, ma capita: il modello, sotto pressione di context o di token budget, scrive il test e l'implementazione nello stesso ciclo senza far fallire prima il test. La skill ha un meccanismo di auto-controllo (re-leggi il flusso, verifica che RED sia preceduto da esecuzione reale del test che fallisce), ma in qualche caso raro slitta.

Mitigazione: dopo la sessione, controlla che ogni commit RED abbia un commit GREEN distinto subito dopo. Se vedi commit "test + impl insieme", probabilmente la skill ha saltato il gate. Riformula la richiesta ("ripeti il ciclo RED-GREEN separatamente per la feature X").

Cosa ho imparato scrivendo questo articolo

La cosa che mi ha colpito è quanto facilmente il TDD "vero" si distingue dal test theatre se sai cosa cercare. Prima di adottare la skill, pensavo di fare TDD quando in realtà facevo "implement-then-test" il 70% delle volte. Il sintomo era ovvio a posteriori: i miei test passavano sempre al primo run dopo essere stati scritti. Test che passano sempre al primo run non testano nulla: stanno solo osservando l'esistente.

Secondo insight: TDD non è universalmente conveniente. Ho perso tempo nei primi mesi cercando di applicarlo a UI components dove non aveva senso. La regola "TDD per business logic, standard per il resto" è quella che mi ha permesso di sostenere la pratica per un anno senza fatica.

Anti-pattern evitati

  • Forzare TDD su tutto. Costo alto, valore basso su UI puro e glue code. Meglio una rule chiara nel CLAUDE.md di progetto che dichiara dove TDD si applica e dove no.
  • RED senza esecuzione reale del test. Il "vedi che fallisce" deve essere letterale (esegui il test, leggi l'output). Se la skill o il modello saltano la verifica, hai test theatre dal primo ciclo. Trust ma verifica.

Una storia ricevuta che vale la pena condividere

Sezione segnaposto. Se hai esperienza con TDD strutturato (positiva o frustrata), scrivimi a federico@federicocalo.dev. Le storie reali pseudonimizzate entrano nei cluster successivi.


← Cluster precedente: Debugging skill Prossimo cluster: Code-reviewer skill →

Domande frequenti

Come si differenzia il Test-Driven Development (TDD) generato da un LLM "a istinto" dal TDD vero e proprio?

Il TDD generato da un LLM "a istinto" produce test che documentano il comportamento corrente del codice, mentre il TDD vero e proprio impone l'ordine corretto, cioè il test esiste e fallisce prima che l'implementazione venga scritta.

Qual è la principale differenza tra il ciclo RED-GREEN-REFACTOR applicato con la skill e senza di essa?

Il ciclo RED-GREEN-REFACTOR applicato con la skill formalizza un loop come tre gate espliciti, mentre senza di essa non esistono questi gate espliciti.

In quali casi è meglio non utilizzare la skill per il TDD?

È meglio non utilizzare la skill quando ci sono overhead, come nel caso del test theatre o della UI, glue, CRUD.

Come si può mitigare il problema in cui il modello "bara" e scrive il test e l'implementazione nello stesso ciclo senza far fallire prima il test?

La mitigazione consiste nel controllare che ogni commit RED abbia un commit GREEN distinto subito dopo, e se si vede un commit "test + impl insieme", probabilmente la skill ha saltato il gate.

Fonti e approfondimenti

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti