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:
- "Scrivi una funzione che fa X"
- Modello scrive l'implementazione.
- "Ora scrivi i test."
- 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):
- 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.
- 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.
- 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:
- UI puro: componenti Angular/React che renderizzano dati senza logica interna. Snapshot test o visual regression test sono sufficienti; TDD strutturato è overkill.
- Glue code: codice che orchestra chiamate fra librerie esistenti (es. fetch + format + display). Il valore del test è basso rispetto al costo di scriverlo.
- 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.createchiamato 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ì:
- RED 1 (happy path): test integration che chiama l'endpoint con payload valido, attende 200 + response body specifico.
- GREEN 1: implementa controller + use case minimo che produca quel response.
- RED 2 (validation 400): test integration con payload invalido, attende 400 + error message specifico.
- GREEN 2: aggiungi validation + error handling.
- RED 3 (auth 401): test con JWT mancante o scaduto, attende 401.
- GREEN 3: aggiungi guard auth.
- 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:
-
Async test:
async functionnel test +awaitsulla chiamata, mai callback puri. - Timeout esplicito: ogni test async dichiara un timeout sensato (5-30 sec). Test che superano timeout = fail deterministico.
- 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
- La feature ha input/output definiti e osservabili? (sì = TDD; no = standard execute)
- È business logic (calcoli, validazioni, transformations, algoritmi)? (sì = TDD)
- È UI puro, glue code, o CRUD semplice? (sì = standard execute)
- 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:
- Partire sempre da un sample dataset reale (non sintetico).
- Test di "shape" (struttura output) prima di test di "value" (correttezza valori).
- 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.
Commenti
Caricamento commenti...
Accedi per commentare