modulo 13 di 24 / Ore 21-40
Moduli 9-16 / Checkpoint 40 ore
Perché testare prima del refactoring
Capire cosa succede quando si fa refactoring senza test: come si introduce una regressione silenziosa, e perché un test scritto prima è la rete di sicurezza del refactoring.
Perché testare prima del refactoring
Questa lezione appartiene al Modulo 13 — Testare l’architettura.
Il rischio concreto
Il refactoring cambia la struttura del codice senza cambiare il comportamento. Ma come si sa che il comportamento non è cambiato?
Senza test, si sa solo guardando la UI manualmente. Il problema: la UI non esercita tutti i casi. È facile dimenticare uno scenario raro, e quel scenario rompe in produzione.
Ecco cosa succede senza test, passo per passo:
Prima del refactoring:
- registraPrestito funziona
- verifica che strumento.stato === "disponibile"
- salva prestito e aggiorna strumento
Durante il refactoring (estrai puoEsserePrestato in funzione separata):
- sposti il controllo dello stato in puoEsserePrestato()
- per sbaglio scrivi strumento.stato === "Disponibile" (D maiuscola)
- la UI mostra "Prestito registrato" anche per strumenti guasti
Dopo il refactoring:
- la UI sembra funzionare (il caso felice è ok)
- il bug è silenzioso — emerge solo con uno strumento guasto
Un test scritto prima avrebbe rilevato il bug immediatamente.
Cosa succede con il test
// Test scritto PRIMA del refactoring
test("uno strumento guasto non può essere prestato", () => {
const archivioStrumenti = creaArchivioStrumentiInMemoria([
{ id: "s1", nome: "Trapano", categoria: "Elettrico", stato: "guasto" }
]);
const archivioPrestiti = creaArchivioPrestitiInMemoria();
const registraPrestito = creaRegistraPrestito({ archivioStrumenti, archivioPrestiti });
expect(() => registraPrestito({ strumentoId: "s1", assegnatario: "Luca 3A" }))
.toThrow("STRUMENTO_NON_DISPONIBILE");
});
// Ora esegui il refactoring.
// Dopo il refactoring, riesegui il test.
// Se fallisce → hai introdotto una regressione.
// Se passa → il comportamento è invariato.
Il test è la rete di sicurezza. Non elimina il refactoring sbagliato — lo rileva subito.
Tre scenari da testare PRIMA di refactorare
Quando si fa refactoring su una funzione del Registro, questi tre scenari devono essere coperti da test prima di iniziare:
Scenario 1: il caso felice
test("registra un prestito per uno strumento disponibile", async () => {
const archivioStrumenti = creaArchivioStrumentiInMemoria([
{ id: "s1", nome: "Trapano", categoria: "Elettrico", stato: "disponibile" }
]);
const archivioPrestiti = creaArchivioPrestitiInMemoria();
const registraPrestito = creaRegistraPrestito({ archivioStrumenti, archivioPrestiti });
await registraPrestito({ strumentoId: "s1", assegnatario: "Luca 3A" });
const strumentoAggiornato = archivioStrumenti.trovaPerId("s1");
expect(strumentoAggiornato.stato).toBe("in_prestito");
});
Scenario 2: la regola violata
test("non registra un prestito per uno strumento guasto", async () => {
const archivioStrumenti = creaArchivioStrumentiInMemoria([
{ id: "s1", nome: "Trapano", categoria: "Elettrico", stato: "guasto" }
]);
const registraPrestito = creaRegistraPrestito({
archivioStrumenti,
archivioPrestiti: creaArchivioPrestitiInMemoria()
});
await expect(registraPrestito({ strumentoId: "s1", assegnatario: "Luca 3A" }))
.rejects.toThrow("STRUMENTO_NON_DISPONIBILE");
});
Scenario 3: l’oggetto non trovato
test("non registra un prestito per uno strumento inesistente", async () => {
const archivioStrumenti = creaArchivioStrumentiInMemoria([]); // archivio vuoto
const registraPrestito = creaRegistraPrestito({
archivioStrumenti,
archivioPrestiti: creaArchivioPrestitiInMemoria()
});
await expect(registraPrestito({ strumentoId: "str-999", assegnatario: "Luca 3A" }))
.rejects.toThrow("STRUMENTO_NON_TROVATO");
});
Con questi tre test scritti prima, il refactoring è protetto. Se dopo il refactoring tutti e tre passano, il comportamento è invariato.
Quando NON serve scrivere test prima
Non ogni riga di codice ha bisogno di un test prima di cambiare:
- rinomino di variabile o funzione → il comportamento non cambia, basta verificare che compili
- cambiamento di commento o documentazione → nessun test necessario
- aggiunta di
console.logtemporaneo → nessun test necessario
I test prima del refactoring servono quando si cambia la struttura di una funzione che contiene logica di dominio o di coordinamento (caso d’uso).
Il workflow corretto
1. Identifica la funzione da refactorare
2. Scrivi (o verifica che esistano) test per: caso felice, regola violata, oggetto non trovato
3. Esegui i test → devono passare tutti (se non passano, il codice era già rotto)
4. Esegui il refactoring
5. Riesegui i test → devono ancora passare tutti
6. Se un test fallisce → il refactoring ha introdotto una regressione → risolvi
Procedura guidata
- Scegli la funzione che vuoi refactorare nel Registro.
- Scrivi i tre test (caso felice, regola violata, oggetto non trovato) prima di toccare il codice.
- Esegui i test — devono passare.
- Esegui il refactoring pianificato.
- Riesegui i test — devono ancora passare.
Attività in classe
## Test scritti prima del refactoring
Funzione da refactorare:
Test 1 — caso felice:
- Scenario:
- Comportamento atteso:
- Passa prima del refactoring (sì/no):
Test 2 — regola violata:
- Scenario:
- Comportamento atteso:
- Passa prima del refactoring (sì/no):
Test 3 — oggetto non trovato:
- Scenario:
- Comportamento atteso:
- Passa prima del refactoring (sì/no):
Refactoring eseguito:
Test dopo il refactoring (tutti passano? sì/no):
Spiegazione guidata per studiare
Il refactoring senza test è come spostare i mobili al buio: probabilmente va bene, ma non lo sai finché non accendi la luce. Il test è la luce.
Il punto centrale è: il test scritto prima non è documentazione — è una rete di sicurezza. Se il test passa prima e dopo il refactoring, hai la prova che il comportamento non è cambiato. Se fallisce dopo, hai trovato esattamente dov’è il problema.
Il rischio tipico è pensare “so cosa fa questo codice, non ho bisogno di test”. Questa confidenza è tradita dal bug più sottile: una maiuscola al posto di una minuscola, un operatore === invece di ==, un return nel posto sbagliato. Il test trova questi bug in secondi. La UI manuale li trova in ore — o non li trova affatto.
Passo per passo nel progetto
- Scegli una funzione del dominio o un caso d’uso che vuoi refactorare.
- Prima di toccare il codice: esistono test per questa funzione? Eseguili.
- Se non esistono: scrivi i tre scenari minimi (felice, regola violata, non trovato).
- Verifica che i test passino — se non passano, il codice è già rotto.
- Esegui il refactoring, un passo alla volta.
- Dopo ogni passo: riesegui i test.
Esempio da leggere lentamente
// Situazione: voglio estrarre puoEsserePrestato in una funzione separata.
// Scrivo il test PRIMA di estrarre.
test("puoEsserePrestato lancia per strumento non disponibile", () => {
const guasto = { id: "s1", stato: "guasto" };
const inPrestito = { id: "s2", stato: "in_prestito" };
const disponibile = { id: "s3", stato: "disponibile" };
expect(() => puoEsserePrestato(guasto)).toThrow();
expect(() => puoEsserePrestato(inPrestito)).toThrow();
expect(() => puoEsserePrestato(disponibile)).not.toThrow();
});
// Ora estraggo puoEsserePrestato.
// Rieseguo il test.
// Se passa → il refactoring è corretto.
Prova di comprensione
- Senza test, come puoi sapere che il refactoring non ha rotto nulla?
- Quali tre scenari devono essere coperti prima di refactorare
registraPrestito? - Perché un test che passa prima del refactoring e fallisce dopo è una buona notizia?
Errori da evitare
- scrivere i test dopo il refactoring — non protegge, documenta solo a posteriori;
- testare solo il caso felice — le regressioni si nascondono nei casi limite;
- non eseguire i test prima di iniziare — se il codice era già rotto, il refactoring non è il problema;
- fare refactoring e aggiungere funzionalità nello stesso passo — separa le due operazioni.
Prodotto da consegnare
Può essere:
- tre test scritti per la funzione da refactorare, con verifica che passino prima del refactoring;
- il log di un’esecuzione dei test prima e dopo il refactoring;
- la scheda compilata con i risultati.
Checklist di chiusura
- Ho identificato la funzione da refactorare
- Ho scritto il test del caso felice prima di toccare il codice
- Ho scritto il test della regola violata
- Ho scritto il test dell’oggetto non trovato
- Tutti i test passano prima del refactoring
- Tutti i test passano dopo il refactoring
Domande per la revisione
- I test che hai scritto erano sufficienti per rilevare le regressioni introdotte?
- Hai trovato un bug nel codice esistente mentre scrivevi i test? (Succede spesso.)
- Quale dei tre scenari era più difficile da testare nel tuo progetto?
Risultato atteso
Alla fine lo studente deve saper dire:
Ho scritto questi tre test per [funzione] prima del refactoring:
1. Caso felice: [scenario] → il test passa.
2. Regola violata: [scenario] → il test passa.
3. Oggetto non trovato: [scenario] → il test passa.
Dopo il refactoring, tutti e tre i test passano ancora.
Se questa spiegazione non è possibile, la lezione non è ancora davvero conclusa.