modulo 13 di 24 / Ore 21-40

Moduli 9-16 / Checkpoint 40 ore

Micro-sfida: scrivi un test prima del refactoring

Esercizio guidato: scegliere una funzione del Registro, scrivere il test prima di refactorare, eseguire il refactoring e verificare che il test passi ancora.

20 min Architettura del software

Micro-sfida: scrivi un test prima del refactoring

Questa lezione appartiene al Modulo 13 — Testare l’architettura.

Obiettivo della lezione

Completare il ciclo completo: scegliere una funzione del Registro che vuoi migliorare, scrivere un test che descrive il suo comportamento attuale, fare il refactoring, e verificare che il test passi ancora.

Alla fine hai la prova che il refactoring non ha rotto nulla.

Perché serve nel percorso

Il refactoring senza test è una promessa: “ho cambiato la struttura ma il comportamento è lo stesso”. Il test la trasforma in una verifica. Se il test passa prima e dopo, la promessa è mantenuta.

Nel Registro del laboratorio

Scegli una funzione concreta. In questa lezione usiamo cercaStrumenti — una funzione che spesso contiene troppa logica in un posto solo:

// src/application/cercaStrumenti.js — PRIMA del refactoring (funzione lunga)
export function cercaStrumenti({ nome, categoria, stato }, archivio) {
  const tutti = archivio.trovaTutti();
  return tutti.filter(s => {
    let ok = true;
    if (nome && nome.trim()) {
      if (!s.nome.toLowerCase().includes(nome.trim().toLowerCase())) ok = false;
    }
    if (categoria && categoria !== "tutte") {
      if (s.categoria !== categoria) ok = false;
    }
    if (stato && stato !== "tutti") {
      if (s.stato !== stato) ok = false;
    }
    return ok;
  });
}

Procedura guidata

Passo 1 — Scrivi i test PRIMA di toccare il codice:

// test/application/cercaStrumenti.test.js
import { creaArchivioStrumentiFake } from "../fakes/archivioStrumentiFake.js";
import { cercaStrumenti } from "../../src/application/cercaStrumenti.js";

const strumentiDiTest = [
  { id: "s1", nome: "Oscilloscopio", categoria: "Misura", stato: "disponibile" },
  { id: "s2", nome: "Trapano", categoria: "Utensile", stato: "guasto" },
  { id: "s3", nome: "Oscillatore", categoria: "Misura", stato: "in_prestito" }
];

test("restituisce tutti gli strumenti senza filtri", () => {
  const archivio = creaArchivioStrumentiFake(strumentiDiTest);
  const risultato = cercaStrumenti({}, archivio);
  expect(risultato).toHaveLength(3);
});

test("filtra per nome (case-insensitive, parziale)", () => {
  const archivio = creaArchivioStrumentiFake(strumentiDiTest);
  const risultato = cercaStrumenti({ nome: "oscillo" }, archivio);
  expect(risultato).toHaveLength(2); // Oscilloscopio e Oscillatore
});

test("filtra per categoria", () => {
  const archivio = creaArchivioStrumentiFake(strumentiDiTest);
  const risultato = cercaStrumenti({ categoria: "Misura" }, archivio);
  expect(risultato).toHaveLength(2);
});

test("filtra per stato", () => {
  const archivio = creaArchivioStrumentiFake(strumentiDiTest);
  const risultato = cercaStrumenti({ stato: "guasto" }, archivio);
  expect(risultato).toHaveLength(1);
  expect(risultato[0].id).toBe("s2");
});

test("combina nome e categoria", () => {
  const archivio = creaArchivioStrumentiFake(strumentiDiTest);
  const risultato = cercaStrumenti({ nome: "oscillo", categoria: "Misura" }, archivio);
  expect(risultato).toHaveLength(2);
});

Passo 2 — Verifica che i test passino con il codice originale:

Esegui i test. Devono passare tutti. Se non passano, il codice originale ha un bug — correggilo prima di procedere.

Passo 3 — Fai il refactoring:

// src/application/cercaStrumenti.js — DOPO il refactoring
export function cercaStrumenti({ nome, categoria, stato } = {}, archivio) {
  const tutti = archivio.trovaTutti();
  return tutti
    .filter(s => corrispondeAlNome(s, nome))
    .filter(s => corrispondeAllaCategoria(s, categoria))
    .filter(s => corrispondeAlloStato(s, stato));
}

function corrispondeAlNome(strumento, nome) {
  if (!nome || !nome.trim()) return true;
  return strumento.nome.toLowerCase().includes(nome.trim().toLowerCase());
}

function corrispondeAllaCategoria(strumento, categoria) {
  if (!categoria || categoria === "tutte") return true;
  return strumento.categoria === categoria;
}

function corrispondeAlloStato(strumento, stato) {
  if (!stato || stato === "tutti") return true;
  return strumento.stato === stato;
}

Passo 4 — Verifica che i test passino ancora:

Esegui di nuovo gli stessi test. Devono passare tutti. Il refactoring è sicuro.

Attività in classe

Scegli tu la funzione da refactorare — può essere registraStrumento, chiudiPrestito o apriSegnalazioneGuasto. Segui i quattro passi:

## Micro-sfida: test prima del refactoring

Funzione scelta:

Test scritti prima del refactoring:

Risultato del primo run (tutti passano?):

Refactoring eseguito (descrizione breve):

Risultato del secondo run (tutti passano ancora?):

Cosa è cambiato nella struttura:

Cosa NON è cambiato nel comportamento:

Spiegazione guidata per studiare

Il ciclo corretto del refactoring è:

  1. Scrivi i test che descrivono il comportamento attuale.
  2. Verifica che passino (il codice funziona come pensi).
  3. Fai il refactoring.
  4. Verifica che i test passino ancora (il comportamento non è cambiato).

Se salti il passo 1, il passo 4 non ha senso — non hai nulla da confrontare.

Cosa testare: non testare l’implementazione interna (come è scritto il codice), testa il comportamento osservabile (cosa restituisce, quali errori lancia). Se il test dipende da dettagli interni, diventa fragile e si rompe ad ogni refactoring.

Esempio di test fragile (da evitare):

// FRAGILE: testa l'implementazione interna
test("chiama trovaTutti una sola volta", () => {
  const archivio = { trovaTutti: jest.fn(() => []) };
  cercaStrumenti({}, archivio);
  expect(archivio.trovaTutti).toHaveBeenCalledTimes(1); // dipende da come è scritto
});

Esempio di test robusto:

// ROBUSTO: testa il comportamento
test("restituisce lista vuota se nessuno strumento corrisponde", () => {
  const archivio = creaArchivioStrumentiFake([
    { id: "s1", nome: "Trapano", categoria: "Utensile", stato: "disponibile" }
  ]);
  const risultato = cercaStrumenti({ categoria: "Misura" }, archivio);
  expect(risultato).toHaveLength(0);
});

Passo per passo nel progetto

  1. Scegli una funzione in src/application/ da refactorare.
  2. Crea (o apri) il file di test corrispondente in test/application/.
  3. Scrivi almeno 3 test che coprono: caso felice, filtro specifico, caso limite (lista vuota).
  4. Esegui i test e verifica che passino.
  5. Fai il refactoring (estrai funzioni, semplifica condizioni, rimuovi duplicazioni).
  6. Esegui di nuovo i test — devono passare tutti.
  7. Documenta nel commit: “refactoring di X — comportamento invariato, test passano”.

Prova di comprensione

  • Perché i test vanno scritti prima del refactoring e non dopo?
  • Cosa significa che un test è “fragile”?
  • Se un test fallisce dopo il refactoring, come sai se il problema è nel test o nel codice?
  • Quanti test sono sufficienti prima di un refactoring?

Errori da evitare

  • scrivere i test dopo il refactoring — non dimostrano nulla sul comportamento originale;
  • testare l’implementazione interna invece del comportamento — i test diventano ostacoli;
  • fare il refactoring e i test in un unico commit — non si capisce cosa ha rotto cosa;
  • saltare il run dei test dopo il refactoring — il punto centrale della micro-sfida.

Prodotto da consegnare

I test scritti prima del refactoring (con il run iniziale che passa), il codice refactorizzato e il run finale che passa. Due commit separati: uno con i test, uno con il refactoring.

Checklist di chiusura

  • i test sono stati scritti prima del refactoring
  • il primo run dei test passa (il codice originale funziona come atteso)
  • il refactoring è stato fatto in un commit separato
  • il secondo run dei test passa (il comportamento non è cambiato)
  • i test coprono almeno il caso felice e un caso limite

Risultato atteso

Ho scelto cercaStrumenti come funzione da refactorare.
Ho scritto 5 test prima del refactoring — tutti passano.
Ho estratto le funzioni di filtro (corrispondeAlNome, corrispondeAllaCategoria, corrispondeAlloStato).
Il secondo run dei test passa ancora — tutti e 5.
Il comportamento è invariato, la struttura è più leggibile.