---
name: tdd-guide-skill
description: Enforce RED-GREEN-REFACTOR ciclo strict per nuove feature e bug fix (test first sempre, no skipping)
category: testing
triggers:
  - "new feature"
  - "implement"
  - "bug fix"
  - "TDD"
  - "test first"
  - "scrivi un test"
  - "aggiungi feature"
license: MIT
author: Federico Calò
---

# TDD guide skill

> Forza Claude al ciclo **RED → GREEN → REFACTOR** strict. Niente
> implementazione senza test failing prima. Niente skip "test dopo".

## When to invoke

Auto-invoke quando l'utente:

- Chiede di implementare nuova feature
- Chiede di fixare bug riproducibile
- Chiede esplicitamente TDD
- Aggiunge endpoint REST / use case / domain logic

**Anti-trigger**: doc-only update, refactor puro (no behavior change),
spike/POC dichiarato.

## Steps

### 1. RED — scrivi test failing

- Identifica il **comportamento osservabile** atteso (input → output)
- Scrivi 1 test che ATTUALMENTE FAIL (no implementazione esistente)
- Run il test e **conferma che fallisce** (e che fallisce per il motivo
  corretto, non per typo)
- Commit: `test(scope): add failing test for <feature>`

### 2. GREEN — implementa minimo per passare

- Scrivi codice **strettamente necessario** per far passare il test
- NIENTE feature extra "while we're here"
- NIENTE refactor di codice esistente in questo step
- Run test → **deve passare**
- Commit: `feat(scope): implement <feature>`

### 3. REFACTOR — pulisci se necessario

- Rimuovi duplicazione, estrai costanti, rinomina per chiarezza
- Test devono **continuare a passare** (run dopo ogni cambio)
- Se test rompe → undo refactor (non era safe)
- Commit (se cambi): `refactor(scope): clean up <feature>`

### 4. Repeat

Una feature = N cicli RED-GREEN-REFACTOR. Ogni ciclo testa **un piccolo
incremento di comportamento**. Mai più di 1 ora per ciclo (sennò scope troppo
largo).

## Anti-patterns (CRITICAL)

- ❌ **Scrivere test dopo l'implementazione** ("aggiungo test al fix"). Test
  retroattivo è validation, non TDD. La pressione di design viene dal test
  scritto PRIMA.
- ❌ **Test che passa subito al primo run**. Significa che testavi qualcosa di
  diverso o c'era già implementazione. Verifica.
- ❌ **GREEN con codice over-engineered** (general purpose framework per 1
  feature). Minimum viable code.
- ❌ **REFACTOR con feature change**. Refactor = stessa behavior, codice migliore.
- ❌ **Skip GREEN commit** ("è una riga, non vale commit"). Atomic commits
  permettono bisect + rollback.

## Test layer guidance

Per stack Java+Spring+Angular+FastAPI:

| Layer | Tool | Tipo |
|-------|------|------|
| Domain (pure) | JUnit + AssertJ | Unit |
| Use case (orchestration) | JUnit + Mockito | Unit con mock |
| Repository | JUnit + Testcontainers | Integration (real DB) |
| REST controller | Spring `@WebMvcTest` o `@SpringBootTest` | Integration |
| Frontend service | Karma + Jasmine | Unit |
| Frontend component | Karma + Jasmine + ComponentFixture | Unit |
| E2E critical flows | Playwright | E2E browser |
| FastAPI endpoint | pytest + TestClient | Integration |

Vedi [red-green-refactor.md](references/red-green-refactor.md) per esempi
estesi per ogni layer.

## Coverage target

- 80%+ line coverage per nuovo codice
- 100% per domain logic (no scuse, è la business rule)
- 60%+ per integration test (path principali, non edge case esotici)
- 1+ e2e per critical flow (login, checkout, search, ecc.)

## References

- [red-green-refactor.md](references/red-green-refactor.md) — pattern di
  esecuzione del ciclo con esempi cross-language
- [test-pyramid.md](references/test-pyramid.md) — distribuzione test per
  layer (unit / integration / e2e) e quando usare ogni tipo

## Esempio invocazione

> **User**: "aggiungi endpoint POST /api/v1/articles/{id}/comments"
>
> **Claude (con tdd-guide-skill)**:
> 1. RED: scrivo `CommentControllerTest.shouldReturn201WhenValidCommentPosted()`
>    → run → fail (controller non esiste)
> 2. GREEN: implemento minimo CommentController + use case → run → pass
> 3. REFACTOR: estraggo CommentMapper da inline → run → pass
> 4. RED prossimo ciclo: shouldReturn400WhenContentEmpty → ...
