Passa al contenuto principale

Testing

Principio

I test non sono una fase separata dello sviluppo: sono parte integrante del codice. Un blocco di logica senza test non è finito — è una bozza.

Tipi di test

Test di integrazione — testano il comportamento del sistema attraverso i suoi confini reali: database, API, file system. Sono i test più importanti perché verificano che i pezzi funzionino insieme. Si usano database reali, non mock.

Test unitari — testano una singola unità di logica in isolamento. Si usano quando la logica è complessa e il test di integrazione da solo non dà abbastanza granularità. Non si scrivono test unitari per coprire codice banale.

Test end-to-end — testano flussi completi dall'interfaccia utente all'infrastruttura. Costosi da mantenere: si usano solo per i percorsi critici.

Regole

Non si mocka il database nei test di integrazione. I mock danno falsa sicurezza: il test passa, ma il sistema in produzione può fallire per ragioni che il mock non intercetta — constraint, transazioni, comportamenti specifici del motore. Si usa sempre un database reale, identico a quello di produzione per tipo e configurazione. Con Entity Framework Code First, il database di test si inizializza applicando le migration sui sorgenti: stessa struttura, stessi vincoli, zero scostamenti.

Un caso d'uso, almeno un test. La business logic non si considera completata finché non esiste almeno un test per ogni caso d'uso, inclusi i casi eccezionali rilevanti. Vale per il percorso principale e per i fallimenti che il sistema deve gestire esplicitamente.

I test sono codice di produzione. Si applicano le stesse regole di qualità: leggibilità, manutenibilità, nessuna logica duplicata. Un test difficile da leggere è un test che non si capirà quando fallirà.

I test devono essere deterministici. Un test che passa a volte e fallisce altre è peggio di nessun test: introduce rumore e toglie fiducia alla CI. Se un test è flaky, si risolve il problema — non si ritenta.

La CI è il guardiano. Nessuna modifica arriva in produzione senza aver passato la suite di test completa. Se la CI è rotta, blocca tutto: nessun workaround, nessuna eccezione.

Test verticali: servizi e casi d'uso

I test di integrazione possono essere scritti a due livelli, entrambi validi:

Verticale sul servizio — testa un singolo servizio di dominio in isolamento, senza passare dal caso d'uso. Utile quando la logica del servizio è complessa e merita una copertura granulare. Il SaveChanges() viene chiamato nel test stesso, perché il servizio non lo fa.

Verticale sul caso d'uso — testa il flusso completo: dal command al SaveChanges(), passando per tutti i servizi coinvolti. È il test più rappresentativo del comportamento reale del sistema.

Entrambi i livelli sono utili e non si escludono. Un caso d'uso ben coperto a livello di test di integrazione non elimina il valore di test più granulari sui servizi con logica complessa.

Generazione di test con l'IA

Quando il modello di dominio è scritto in Entity Framework Code First e i casi d'uso sono documentati, l'IA ha tutto il contesto per generare test di integrazione significativi: conosce le entità, le relazioni, i vincoli e il comportamento atteso.

Questo non sostituisce il ragionamento del developer sui casi limite, ma abbassa drasticamente il costo di bootstrapping — il test generato è un punto di partenza solido, non un template vuoto.

Testare la solidità del modello

Un sistema è solido nel momento in cui resiste ai tentativi di romperlo. I test devono verificare non solo che le operazioni valide funzionino, ma che le operazioni invalide vengano rifiutate: constraint violati, inserimenti incoerenti, stati impossibili. Se la scrittura è il gate, i test devono certificare che il gate regge.

Bisogna conoscere i modi in cui una cosa si può rompere — tendenzialmente dovrebbero essere zero. I test, oltre a fare documentazione, certificano il livello di solidità del sistema.

Con l'IA, generare test che tentano di rompere il modello è semplice e veloce: dato lo schema EF e i constraint, l'IA può produrre batterie di test che verificano ogni vincolo strutturale.

Coverage

Il coverage è un indicatore, non un obiettivo. Un coverage alto su codice banale è rumore. Un coverage basso su business logic critica è un rischio.

L'obiettivo è coprire ogni comportamento significativo, non ogni riga. La domanda giusta non è "quanto codice è coperto?" ma "cosa succederebbe se questa logica fosse sbagliata e non lo sapessi?".