modulo 07 / lezione 52
Leggere e verificare codice generato
Test automatici minimi: unit, integration, smoke
Capire quali test automatici minimi danno fiducia senza richiedere una strategia enterprise.
Test automatici minimi: unit, integration, smoke
Test automatici sono un investimento. Troppo pochi: niente protezione contro regressioni. Troppi: tempo speso su test che non aggiungono valore. La via media di “test minimi che bastano” è strategica: copri ciò che dà fiducia, salti ciò che è costoso da testare e poco rischioso.
Perché questa lezione conta
Vibe coder che fa solo prototipi può saltare test. Vibe coder che pubblica e mantiene non può. Senza test, ogni modifica è una scommessa. Saltare il primo livello di test (smoke) è negligenza; sovraingegnerizzare il quinto (test E2E che simulano browser) è procrastinazione. Conoscere i tre livelli minimi e quale usare quando è la mappa pratica.
Obiettivo operativo
Alla fine devi saper:
- distinguere unit, integration, smoke test e cosa coprono;
- scrivere test minimi per una feature appena generata;
- valutare quando sono sufficienti e quando serve di più.
Nucleo della lezione
1. Tre livelli, tre coperture
Unit test: singola funzione, in isolamento. Niente DB, niente rete, niente filesystem. Test molto veloci, granulari.
Cosa coprono: logica pura. Validazione, calcoli, trasformazioni, parsing. Esempi: formatPrice(1999, 'IT') → "€19,99". validateEmail('foo') → { valid: false, error: 'Missing @' }.
Quando: per ogni funzione con logica non banale. Salta funzioni-glue (dispatcher, wrapper).
Tooling: vitest, jest, mocha, deno test, bun test. Quasi sempre già nello stack.
Integration test: più componenti interagiscono. Spesso include DB reale (test DB), client HTTP (mockato o reale), servizi esterni (mockati).
Cosa coprono: flussi attraverso più moduli. Endpoint API end-to-end (request → handler → DB → response). Componente UI con il suo state e le chiamate API mockate.
Quando: per ogni endpoint API critico, per ogni flusso UI principale.
Tooling: vitest + supertest (Node), pytest + TestClient (FastAPI), MSW (mock service worker per UI).
Smoke test: l’app parte? Le pagine principali rispondono? I servizi si connettono?
Cosa coprono: failure modes catastrofici. “Build rotta”, “DB down”, “env mancante”, “porta occupata”.
Quando: a ogni deploy. Spesso integrato nel CI come step finale.
Tooling: curl + script bash, oppure Playwright/Cypress per smoke UI.
2. Cosa NON testare
Errore frequente è testare tutto. Non vale la pena per:
Wrapper banali: function getName(user) { return user.name; }. Testarlo non aggiunge fiducia.
Configurazione: tsconfig, eslintrc. Test inutile.
Dipendenze esterne: testare che axios.get funzioni. È loro responsabilità.
UI puramente presentazionale: “il bottone si vede”. Visual regression test esiste ma è oltre minimo.
Logica triviale: add(a, b). A meno che la logica abbia edge case (overflow, null), saltare.
Codice che probabilmente cambierà presto: testare prototipo che butterai = lavoro perso.
Pareto: 20% del codice produce 80% del rischio. Test mirati lì.
3. Test scritti con AI
L’AI è ottima a generare test, con cautele:
Cosa l’AI fa bene:
- scaffolding (struttura file test, setup/teardown);
- enumerare casi limite plausibili (string vuota, numero negativo, ecc.);
- generare assertion partendo da signature.
Cosa controllare:
- Test che non assertano nulla:
expect(result).toBeDefined()su funzione che ritorna sempre qualcosa. Inutile. - Test troppo legati all’implementazione: assert su nomi di metodi interni invece che su comportamento osservabile. Si rompono al primo refactor.
- Mock eccessivi: tutto mockato → test di niente. Mock il minimo, real il resto.
- Assertion deboli:
expect(arr.length).toBeGreaterThan(0)quando volevitoBe(3). - Coverage finta: 100% righe coperte, ma assertion superficiali.
Pratica: leggi ogni test generato e chiediti “se rompo la logica, questo test fallisce?”. Se no, riscrivi assertion.
Caso guidato
Feature: funzione calculateTotal(cart) che somma prodotti e applica sconto.
Test inutile generato dall’AI:
it("calculates total", () => {
expect(calculateTotal).toBeDefined();
});
Passa sempre, ma non protegge comportamento. Test minimo utile:
it("sums item prices and applies percentage discount", () => {
const cart = {
items: [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 1 },
],
discountPercent: 10,
};
expect(calculateTotal(cart)).toBe(22.5);
});
Poi aggiungi casi essenziali:
- carrello vuoto = 0;
- quantità 0 rifiutata o ignorata, secondo specifica;
- sconto oltre 100 non accettato;
- arrotondamento dichiarato.
Smoke test diverso:
Avvio app, apro pagina carrello, vedo titolo e nessun errore console.
Unit test protegge logica. Smoke test protegge avvio. Integration test protegge collaborazione tra parti. Non confonderli: se un test prova tutto, spesso non prova bene nulla.
Procedura pratica
Per nuova feature:
- Identifica core logic: cosa è critico, cosa è glue.
- Unit test core: 3-5 test per funzione critica. Casi normale + 2-3 edge.
- Integration test: 1-2 test per ogni endpoint/flusso significativo.
- Smoke test: build + start + 1 chiamata “/health” + 1 page principale carica.
- CI: tutti i test eseguono a ogni push.
- Rosso = blocca: PR non si mergia con test rossi.
Tempo medio per feature: 30-90 minuti di test. Recuperati nelle settimane successive evitando regressioni.
Laboratorio guidato
Feature: funzione validatePassword(password: string) che restituisce {valid: boolean, errors: string[]}. Regole: min 8 caratteri, almeno 1 numero, almeno 1 lettera maiuscola.
Unit test:
import { validatePassword } from './validation'
describe('validatePassword', () => {
it('accetta password valida', () => {
expect(validatePassword('Test1234')).toEqual({ valid: true, errors: [] })
})
it('rifiuta password troppo corta', () => {
const r = validatePassword('Tes1')
expect(r.valid).toBe(false)
expect(r.errors).toContain('min_length')
})
it('rifiuta password senza numero', () => {
const r = validatePassword('TestTest')
expect(r.valid).toBe(false)
expect(r.errors).toContain('missing_number')
})
it('rifiuta password senza maiuscola', () => {
const r = validatePassword('test1234')
expect(r.valid).toBe(false)
expect(r.errors).toContain('missing_uppercase')
})
it('cumula errori', () => {
const r = validatePassword('test')
expect(r.errors.length).toBe(3)
})
it('gestisce stringa vuota', () => {
const r = validatePassword('')
expect(r.valid).toBe(false)
})
})
6 test, ~20 righe. Coprono regole + caso limite stringa vuota.
Integration test (se esiste endpoint POST /signup che usa validatePassword):
import { app } from './app'
import { request } from 'supertest'
it('signup respinge password debole', async () => {
const res = await request(app)
.post('/signup')
.send({ email: 'test@test.it', password: 'weak' })
expect(res.status).toBe(400)
expect(res.body.errors).toContain('password')
})
Smoke (script):
#!/bin/bash
set -e
npm run build
npm start &
SERVER_PID=$!
sleep 3
curl -f http://localhost:3000/api/health
curl -f http://localhost:3000/
kill $SERVER_PID
Smoke verifica build + start + 2 chiamate. Se passa, deploy probabilmente ok.
Output atteso
Per la prossima feature:
- 3-5 unit test per logica core;
- 1-2 integration test per flusso principale;
- 1 smoke test che verifichi che l’app funzioni;
- script CI che li esegua tutti.
Studio passo passo
Studia questa lezione in tre passaggi, senza correre subito al prompt o al codice.
- Prima lettura: chiarisci l’obiettivo: capire quali test automatici minimi danno fiducia senza richiedere una strategia enterprise. Se non riesci a riscriverlo in una frase tua, rileggi il nucleo della lezione.
- Seconda lettura: trasforma i contenuti in domande operative:
- Unit test per logica pura, integration test per collaborazione tra parti, smoke test per capire se l’app parte.
- Dove l’AI aiuta: generare casi, scaffolding, assertion, mock semplici, comandi di test.
- Dove verificare: test che non assertano nulla, test troppo legati all’implementazione, copertura finta.
- Applicazione: svolgi il laboratorio: aggiungere un test unitario e uno smoke test a una mini feature generata. L’output da ottenere è: una base di test minima che protegge il prossimo ciclo debug/fix.
Prima di passare alla lezione successiva, controlla di avere un artefatto concreto: una tabella, un prompt, una checklist, un mini report, un diff commentato o una decisione scritta. Nel vibe coding la comprensione resta astratta finché non produce una traccia verificabile.
Checklist di verifica
- distinguo unit, integration, smoke;
- so cosa NON testare (saltare wrapper, dipendenze esterne, ecc);
- ho almeno una feature recente con test ai 3 livelli;
- verifico assertion (non
toBeDefined()per default); - CI esegue test a ogni push.
Errori comuni
Niente test. “Lo provo a mano”. Manca regressione.
Solo unit, niente integration. Funzioni ok, flusso completo rotto.
Tutto E2E. Test lentissimi, fragili, difficili da debuggare.
Mock di tutto. Test che non testa nulla.
Test su implementazione, non comportamento. Si rompono al primo refactor sano.
Coverage inseguita per numero. 80% coverage con assertion deboli < 60% con assertion forti.
Test scritti dall’AI accettati senza review. Spesso assertion deboli, sempre review.
Prompt di lavoro
Genera test automatici minimi per questa funzione/feature:
[INCOLLA codice della funzione/endpoint]
Stack test: [vitest / jest / pytest / ...]
Voglio:
1. Unit test (3-5 casi):
- caso normale
- 2-3 edge case (vuoto, null, valore limite)
- 1 caso di errore atteso
2. Integration test (se applicabile):
- 1 test che verifica flusso end-to-end con dipendenze reali (DB, fetch) o mock realistici
3. Smoke (se applicabile):
- 1 controllo che la feature è raggiungibile dopo deploy
Vincoli:
- assertion specifiche (toBe, toEqual), no toBeDefined() di default
- testano comportamento, non implementazione
- niente mock di cose che potrei testare reali a basso costo
- niente coverage finta
Output: codice test eseguibile + breve commento spiegando cosa NON ho testato (per onestà sulla copertura).
Esercizio finale
Per la tua prossima feature: scrivi test ai 3 livelli prima o subito dopo la generazione del codice. Misura: quanto tempo? Quante volte i test hanno catturato regressioni nei giorni successivi? Tipicamente 30-60 min di test salvano 2-4 ore di debugging in produzione. ROI evidente in poche settimane.