Dove tracciare il confine tra unit test e integration test
· Aggiornato il: · Go Komura · Testing, Unit Testing, Integration Testing, Test Design, Sviluppo Windows, C# / .NET
Nelle discussioni sul design dei test, la domanda silenziosamente difficile ogni volta è quanto spingere negli unit test e da dove promuovere le cose negli integration test.
Le posizioni pericolose sono i due estremi:
- “Vogliamo un loop veloce, quindi tutto diventa unit test”
- “È più vicino alla realtà, quindi tutto diventa integration test”
Il primo finisce pieno di mock e perde facilmente i punti che si rompono in produzione; il secondo tende a produrre una suite lenta e fragile. In pratica, gli assi da guardare sono un po’ più chiari:
- Quel che vuoi verificare è la tua logica, o il cablaggio verso l’esterno?
- Se sostituisci con un fake in-memory, il significato sopravvive?
- Il comportamento reale di DB / file / HTTP / DI / configurazione / framework / OS è il soggetto vero?
- Vuoi eseguire un gran numero di pattern di input velocemente?
Una volta visibili queste quattro, il confine tra unit test e integration test diventa molto più facile da tracciare.
In questo articolo organizziamo il confine tra unit test e integration test con un taglio pratico, basato sulle informazioni pubbliche di Microsoft Learn e Martin Fowler disponibili a marzo 2026.123
1. La conclusione prima di tutto
Per dirla grossolanamente, ma in una forma facile da usare in pratica:
- La logica pura va negli unit test
- Connessioni, cablaggio, conversioni e differenze di ambiente vanno negli integration test
- Se entrambi potrebbero verificarlo, inizia con un unit test
- Piuttosto che rendere gli integration test ampi e pesanti, restringili al confine
In una frase: gli unit test sono “test sulle decisioni”, gli integration test sono “test sulle connessioni”.
Cose il cui significato è completo senza risorse esterne — calcoli prezzi, transizioni di stato, validazione input, condizioni di approvazione, classificazione eccezioni — sono più veloci, meno fragili, e possono coprire pattern di input più fittamente quando spinte verso gli unit test. D’altra parte, cose che “ti tradiscono nel momento in cui sono collegate” — esecuzione SQL, serializzazione JSON / CSV, routing, model binding, registrazione DI, lock file, permessi, registrazione COM, 32-bit / 64-bit, STA / MTA — sono più sicure messe dal lato integration test.
Anche Microsoft Learn, in Integration tests in ASP.NET Core, consiglia di restringere gli integration test agli scenari infrastrutturali importanti e di scegliere gli unit test ogniqualvolta bastino.
2. Cosa intendiamo in questo articolo per unit test e integration test
Qui usiamo i termini come segue.
| Livello | Cosa verifica | Setup tipico |
|---|---|---|
| Unit test | Correttezza di una responsabilità isolata | Usa fake / mock / stub e taglia risorse esterne |
| Integration test | Connessioni tra più componenti, e comportamento che include infrastruttura e framework | DB reale, file reali, serializer reale, host reale, pipeline reale, ecc. |
| E2E / functional test | Flussi utente attraverso l’intera app | App deployata, più servizi, browser reale o processo reale |
Nella guida ai test unitari di .NET, un buon unit test è descritto come veloce / isolato / ripetibile, senza dipendere da fattori esterni come file system o database. Unit testing best practices for .NET lo espone chiaramente.
Inoltre, un integration test non significa solo “un test pesante che necessariamente usa un altro processo o server”. Anche nello stesso processo, se colleghi insieme più componenti reali e verifichi il comportamento genuino del framework o dell’infrastruttura, quello tende a un integration test.
Ad esempio, quando si testa unitariamente un’azione di controller ASP.NET Core, la guida ufficiale è restringere il soggetto alle decisioni nel corpo dell’azione e gestire interazioni lato framework come routing, model binding e filters negli integration test. Vedi Unit test controller logic in ASP.NET Core per una chiara ripartizione.
3. La tabella decisionale a colpo d’occhio
Per prima cosa, la tabella più utile in pratica.
| Cosa vuoi verificare | Test primario | Note |
|---|---|---|
| Calcolo prezzi, sconti, transizioni di stato, validazione input | Unit test | Vuoi correre i pattern di input fittamente |
| Classificazione eccezioni, selezione messaggi errore, decisione se fare retry | Unit test | Il significato è completo senza I/O reali |
| SQL repository / traduzione ORM, transazioni | Integration test | Il comportamento del DB reale o provider reale è il soggetto |
| Serializzare / deserializzare JSON / XML / CSV | Integration test | Il drift del wire-format è difficile da trovare con fake |
| Routing, model binding, filters, middleware | Integration test | Verificare la connessione al framework |
| Transizioni di stato di ViewModel o Presenter WPF / WinForms | Unit test | Significative senza alzare l’UI |
| Binding reale, Dispatcher, lifecycle controlli, message loop | Integration test o UI test | Il comportamento del framework e del thread è il soggetto |
| Percorsi file, permessi, lock, cartelle condivise, line endings, encoding | Integration test | Serve il comportamento reale di OS e file system |
| Registrazione COM, 32-bit / 64-bit, STA / MTA, sorgente caricamento DLL | Integration test | Le differenze di ambiente e i confini di processo sono il soggetto |
| Startup dell’app intera, check end-to-end dei casi d’uso principali | E2E / smoke | Bastano pochi |
Il trucco per leggere questa tabella è chiedersi quale test è più vicino al “motivo per cui si rompe in produzione”. Decidere in base all’incertezza che vuoi ridurre, piuttosto che a dove vive il codice, evita di deragliare.
4. Cosa dovrebbero possedere gli unit test
Quel che si adatta agli unit test è responsabilità il cui significato rimane dopo aver rimosso il mondo esterno.
Ad esempio:
- Business rules
- Branching
- Transizioni di stato
- Validazione input
- Classificazione errori
- Decidere la politica di retry
- Cambiamenti di stato di ViewModel / Presenter
- La logica di conversione in sé
In particolare, più combinazioni ha una cosa, maggiore è il valore di spingerla negli unit test.
Ad esempio, man mano che le condizioni di branching si moltiplicano —
- coupon presente / assente
- in stock / out of stock
- primo ordine / ordine ripetuto
- amministratore / utente normale
- valore valido / valore al limite / valore invalido
— eseguirle tutte attraverso integration test diventa pesante. Qui è più razionale affettare finemente con unit test.
È importante anche negli unit test mantenere controllabili i fattori esterni.
- Inietta l’ora corrente
- Rendi sostituibili GUID e numeri casuali
- Non attendere con sleep
- Non toccare DB o file reali
- Non uscire sulla rete reale
Quando queste sono rispettate, i test diventano piuttosto stabili.
4.1. Quando i mock si moltiplicano in un unit test
Se, cercando di scrivere un unit test,
- hai bisogno di 7 mock,
- il setup è lungo,
- la sezione arrange è più lunga del corpo, oppure
- non vedi più cosa volevi verificare,
solitamente è una di queste due:
- La classe ha troppe responsabilità
- Stai spingendo un cablaggio che in realtà dovrebbe essere verificato da un integration test dentro un unit test
I mock sono uno strumento per tagliare il mondo esterno — non sono uno strumento per provare che la connessione con la cosa reale è corretta. Confondere i due rende probabile che “tutto è verde ma la produzione crolla”.
5. I quattro confini da promuovere agli integration test
I posti che vale la pena promuovere agli integration test si possono organizzare grossolanamente in quattro: formati, cablaggio, ambiente, tempo.
5.1. Il confine formato
I formati qui includono cose come:
- JSON / XML / CSV
- Schema DB e mapping
- nullable / precision / timezone
- Serializzazione di enum e date
- Encoding caratteri e BOM
- Line endings
Anche Martin Fowler elenca i confini che coinvolgono serializzare / deserializzare come candidati agli integration test. The Practical Test Pyramid è un buon riferimento.
Ad esempio, difetti come
- un DTO serializzato in JSON è uscito con nomi campo diversi,
- quoting o line break CSV sono stati pasticciati,
- un
decimalè stato arrotondato, - la gestione di
DateTimeOffsetnel DB è deragliata, o nullversus empty string si sono comportati diversamente da quanto atteso,
passano facilmente dagli unit test.
5.2. Il confine cablaggio
Il confine cablaggio include parti come queste:
- Registrazione DI
- Binding configurazione
- Routing
- Model binding
- Filters
- Middleware
- Startup host
- Event wiring
- WPF Binding e command hookup
Qui il soggetto non è “la mia funzione è corretta?” ma se più parti reali sono collegate correttamente.
In ASP.NET Core, la guida ufficiale è restringere gli unit test delle azioni del controller alle decisioni dell’azione, e guardare routing, model binding e filters dal lato integration test.
Il ragionamento è lo stesso al di fuori del web: anche in un’app desktop, le transizioni di stato del ViewModel appartengono agli unit test, mentre il comportamento che coinvolge XAML Binding reale o Dispatcher tende agli integration test.
5.3. Il confine ambiente
Nello sviluppo Windows, questo conta molto.
- Permessi file
- Cartelle condivise
- Lock file
- Rename da file temporaneo
- Privilegi amministratore
- Permessi di avvio servizio
- Registrazione COM
- 32-bit / 64-bit
- STA / MTA
- Da dove vengono caricate le DLL
Qui le condizioni dell’OS e dell’ambiente di esecuzione stesso sono i protagonisti. Con fake in-memory il significato in gran parte evapora, quindi è più sicuro coprirli con integration test.
In particolare, in configurazioni che coinvolgono software Windows esistente o COM / ActiveX, è del tutto normale inciampare su registrazione, bitness, modello di threading e permessi prima che la logica abbia mai una chance. Questi fallimenti sono il territorio che gli integration test inclusivi di ambiente colgono — non gli unit test.
5.4. Il confine tempo
Un’altra cosa facile da trascurare è tempo e concorrenza.
- Timeout
- Cancellation
- Comportamento reale dei retry
- Elaborazione guidata da timer
- Arresto del lavoro in background
- Race condition
- Ordine di shutdown
Quel che conta qui è separare le decisioni dal comportamento reale.
Ad esempio,
- quante volte fare retry, e
- quali eccezioni sono retryable
sono perfettamente serviti da unit test. D’altra parte,
- se il timeout scatta davvero,
- se la cancellation si propaga,
- se le cose sopravvivono a una collisione tra timer e lavoro async, e
- se handle e task si chiudono pulitamente allo shutdown
tendono agli integration test.
6. Errori comuni di giudizio
6.1. Mockare il repository e considerarlo fatto
Anche se tutto intorno al repository passa con i mock, non sai ancora
- se l’SQL è corretto,
- se le transazioni hanno effetto,
- se corrisponde allo schema,
- se il mapping deraglia, o
- se encoding e precisione sopravvivono.
Un Repository solitamente è meno un target di test di logica e più un punto di connessione a un confine. In quel caso, aumentare il peso degli integration test rispetto agli unit test corrisponde meglio alla realtà.
6.2. Cercare di coprire il framework in un unit test di controller / endpoint
Quel che vuoi vedere in un unit test di un’azione di controller è grossolanamente
- il branching condizionale,
- la scelta del return value, e
- quali servizi dipendenti vengono chiamati.
Mentre,
- se la route corrisponde,
- se il model binding passa,
- se il filter ha effetto, e
- come appaiono le cose dopo il middleware
appartengono al lato integration test. Mescolare questi rende difficile capire cosa si è rotto.
6.3. Forzare i pattern di input negli integration test
Gli integration test, essendo più vicini alla realtà, sono inevitabilmente più lenti. Quindi conviene dividere: forzare i branchi va agli unit test, i casi rappresentativi al confine vanno agli integration test.
Anche la guida Microsoft sugli integration test raccomanda, per database e file system, di non far correre ogni pattern attraverso gli integration test ma restringersi a scenari rappresentativi come read / write / update / delete.
6.4. Colpire istanze di produzione di servizi esterni direttamente dalla CI
Questo è meglio evitarlo.
La “realtà” conta negli integration test, ma ciò non significa colpire SaaS di produzione o API di produzione a ogni run. Anche Fowler raccomanda di far girare servizi esterni localmente, usare fake, o usare un’istanza di test dedicata.
In pratica, una combinazione di
- DB locale,
- directory temporanee,
- host di test,
- ambiente di test dedicato, e
- fake service con contratto pinnato
è facile da usare.
7. Una struttura consigliata in pratica
Non esiste un rapporto assolutamente corretto. Ma questa struttura a tre strati è ampiamente applicabile.
| Strato | Puntello | Cosa ci va |
|---|---|---|
| Core layer | Unit test fitti | Business rules, transizioni di stato, validazione input, classificazione errori |
| Boundary layer | Integration test stretti | DB, file, HTTP, serializer, DI, configurazione, COM, permessi |
| Whole-app layer | Pochi smoke / E2E | Check di startup, flussi principali, prevenzione regression su incidenti gravi |
Intuitivamente, gli unit test diventano numerosi, gli integration test diventano densi al confine.
Il modo consigliato di procedere:
- Per prima cosa, elenca i confini dell’applicazione
- Sagoma la logica in modo che possa essere tagliata dal mondo esterno
- Per ogni confine, piazza “almeno un happy path” e “un percorso di fallimento rappresentativo”
- Mantieni piccolo il numero di end-to-end
- Quando appare un bug, aggiungi un test nello strato che può riprodurre quel bug al costo minimo
L’ultimo punto, 5, è quello importante.
- Se è un errore di regola, aggiungi un unit test
- Se è un errore SQL / binding / configurazione / permessi / registrazione, aggiungi un integration test
- Se è un fallimento che coinvolge startup o distribuzione, aggiungi uno smoke o E2E
Crescere la suite in questo modo impedisce alle responsabilità dei test di deragliare.
8. Cinque domande da farsi quando si è in dubbio
Infine, ecco cinque domande condensate per controllare in caso di dubbio.
- Se sostituisci con un fake in-memory, il significato che volevi verificare sopravvive?
- Se sì, tendi a un unit test.
- Quando si rompe, sospetteresti i collegamenti o la configurazione piuttosto che la logica?
- Se sì, tendi a un integration test.
- Soggetto vero è il DB / file / serializer / DI / route / model binding / OS / permessi / bitness / thread?
- Se sì, tendi a un integration test.
- Vuoi eseguire un gran numero di pattern di input velocemente?
- Se sì, tendi a un unit test.
- Quando quel test fallisce, è immediatamente chiaro cosa aggiustare?
- Se no, i livelli di test sono mescolati.
Ordinare le cose con queste cinque domande rende più facile evitare il decision-making approssimativo di “integration test perché è vagamente più vicino alla realtà” o “unit test perché è vagamente più veloce”.
9. Riassunto
Il confine tra unit test e integration test è più praticamente deciso non da dove vive il codice, ma da quale incertezza vuoi ridurre.
L’essenziale si riduce a questi cinque.
- Gli unit test sono test sulle decisioni
- Gli integration test sono test sulle connessioni
- Forzare i branchi va agli unit test
- Formati, cablaggio, ambiente e tempo vanno agli integration test
- Copri i check end-to-end dell’app intera con pochi smoke / E2E
Quel che più si vuole evitare sono questi tre:
- Credere che i mock abbiano provato la connessione con la cosa reale
- Cercare di far correre ogni ramo attraverso gli integration test
- Mescolare le responsabilità di unit test e integration test
Quando sei in dubbio, chiediti prima: questo difetto rompe una “decisione” o rompe una “connessione”? Questa sola domanda risolve una buona fetta dei casi.
10. Articoli correlati
- A Minimum Security Checklist for Windows Application Development
- Quanto un’app Windows può davvero essere un singolo binario?
- Quando serve davvero privilegio amministratore su Windows?
- Cos’è Reg-Free COM?
11. Riferimenti
-
Microsoft Learn, Integration tests in ASP.NET Core ↩
-
Microsoft Learn, Unit testing best practices for .NET ↩
-
Martin Fowler, The Practical Test Pyramid ↩
Articoli correlati
Articoli recenti con gli stessi tag per approfondire argomenti vicini.
Come isolare concretamente "solo le operazioni che necessitano dei privilegi di amministratore" in un'app Windows
Una procedura dettagliata concreta per mantenere l'UI dell'app Windows su asInvoker isolando solo le operazioni con privilegi di amminist...
Memorizzazione di segreti nelle app Windows: evitare la configurazione in testo normale con DPAPI
Per evitare di archiviare credenziali di connessione e token API in file di configurazione di testo normale nelle app Windows, esaminerem...
Una lista di controllo minima sulla sicurezza per lo sviluppo di app Windows
Una guida in stile elenco di controllo alle nozioni di base sulla sicurezza per le app aziendali WPF / WinForms / WinUI / C++ / C#: privi...
Icone nella system tray e notifiche toast nelle app Windows — le insidie di NotifyIcon e come scegliere l'AppNotification giusta
Una guida pratica per mantenere un'applicazione Windows aziendale residente nella system tray (area di notifica) e avvisare l'utente tram...
Fino a quando funzioneranno le applicazioni VB6? — Lo stato del supporto al runtime e un percorso pratico verso la migrazione a .NET
Fino a quando continueranno a funzionare le applicazioni VB6? Questo articolo chiarisce l'asimmetria tra la politica di supporto del runt...
Argomenti correlati
Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.
Argomenti tecnici Windows
Portale su sviluppo Windows, analisi dei problemi e valorizzazione delle risorse esistenti.
Servizi collegati all’argomento
L’articolo è direttamente collegato ai servizi seguenti.
Consulenza tecnica e revisione del progetto
Dove tagliare il confine tra unit test e integration test è facile da affrontare come design review pre-implementazione o consulenza su strategia di test.
Sviluppo di applicazioni Windows
Nelle app Windows, confini come file, permessi, COM e 32-bit / 64-bit si mappano direttamente sui layer di test, quindi si sposa bene con lo stabilire l'approccio implementativo.
Domande frequenti
Domande che ricorrono nelle consulenze sull’argomento dell’articolo.
- Qual è la differenza tra unit test e integration test?
- Un unit test verifica la correttezza di una responsabilità isolata, usando fake, mock o stub per tagliare risorse esterne come database e file. Un integration test verifica connessioni tra più componenti e comportamenti che coinvolgono infrastruttura o framework reali, come un database reale, file reali o una pipeline host reale. In una frase: gli unit test sono test sulle decisioni, gli integration test sono test sulle connessioni.
- Come decido se una cosa va in unit test o integration test?
- Chiediti se il significato di ciò che vuoi verificare sopravvive se sostituisci con un fake in-memory. Logica pura come calcoli prezzi, transizioni di stato, validazione input e classificazione errori è completa senza risorse esterne e appartiene agli unit test. Cose che ti tradiscono nel momento in cui sono collegate, come esecuzione SQL, serializzazione JSON o CSV, routing, registrazione DI, lock file, permessi e registrazione COM, sono più sicure come integration test. Se entrambi i livelli potrebbero verificarlo, inizia con un unit test.
- Cosa significa quando un unit test ha bisogno di molti mock?
- Se un unit test richiede sette mock, un setup lungo o una sezione arrange più lunga del corpo del test, solitamente segnala uno di due problemi. O la classe sotto test ha troppe responsabilità, o stai spingendo un cablaggio che in realtà appartiene a un integration test dentro un unit test. I mock sono uno strumento per tagliare il mondo esterno, non per provare che la connessione con la cosa reale è corretta; confondere i due porta a suite tutte verdi mentre la produzione crolla.
- Gli integration test dovrebbero coprire ogni pattern di input?
- No. Gli integration test sono più vicini alla realtà e quindi più lenti, quindi forzare i rami con essi produce una suite lenta e fragile. La divisione pratica è eseguire grandi numeri di pattern di input velocemente negli unit test, e tenere gli integration test ristretti a scenari rappresentativi a ogni confine, come un happy path e un percorso di fallimento rappresentativo. Anche la guida Microsoft consiglia di restringere gli integration test su database e file system a scenari rappresentativi di read, write, update e delete.
Profilo dell’autore
Pagina di presentazione dell’autore dell’articolo.
Go Komura
Rappresentante di KomuraSoft LLC
Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.