C'è, da qualche parte nel tuo codebase, una linea di codice che esegue l'azione effettiva di connessione al DB remoto. Questa riga di codice è, 9 volte su 10, una chiamata a un metodo "built-in" fornito dalle librerie di runtime specifiche per la lingua e l'ambiente. In quanto tale, non è il "tuo" codice e quindi non è necessario testarlo; ai fini di un test unitario, puoi avere fiducia che questa chiamata al metodo funzionerà correttamente. Quello che puoi e dovresti ancora testare nella tua suite di test delle unità sono cose come assicurarsi che i parametri che verranno utilizzati per questa chiamata siano quelli che ti aspetti siano, ad esempio assicurarti che la stringa di connessione sia corretta, o l'istruzione SQL o nome della stored procedure.
Questo è uno degli scopi alla base della limitazione secondo cui i test unitari non devono lasciare la loro "sandbox" di runtime e dipendere dallo stato esterno. In realtà è abbastanza pratico; lo scopo di un test unitario è verificare che il codice che hai scritto (o che stai per scrivere, in TDD) si comporti nel modo in cui pensavi che sarebbe successo. Il codice che non hai scritto, come ad esempio la libreria che stai utilizzando per eseguire le operazioni del tuo database, non dovrebbe far parte dello scope di alcun test unitario, per il semplice motivo che non lo hai scritto.
Nella tua suite di test integration , queste restrizioni sono rilassate. Ora tu puoi test di progettazione che toccano il database, per assicurarti che il codice che hai scritto funzioni bene con il codice che non hai. Queste due suite di test dovrebbero rimanere segregate, tuttavia, poiché la suite di test delle unità è più efficace e più veloce è la sua esecuzione (in modo da poter verificare rapidamente che tutte le asserzioni fatte dagli sviluppatori sul loro codice siano ancora valide) e quasi per definizione, un test di integrazione è più lento di ordini di grandezza a causa delle dipendenze aggiunte alle risorse esterne. Lascia che il build-bot gestisca l'esecuzione della suite di integrazione completa ogni poche ore, eseguendo i test che bloccano le risorse esterne, in modo che gli sviluppatori non si calpestino l'un l'altro eseguendo gli stessi test localmente. E se la costruzione si rompe, e allora? È molto più importante garantire che il build-bot non fallisca mai una build di quanto probabilmente dovrebbe essere.
Ora, quanto rigorosamente puoi aderire a questo dipende dalla tua esatta strategia per la connessione e l'interrogazione del database. In molti casi in cui è necessario utilizzare il framework di accesso ai dati "bare-bones", come gli oggetti SqlConnection e SqlStatement di ADO.NET, un intero metodo sviluppato dall'utente può consistere in chiamate di metodi incorporate e altro codice che dipende dall'avere un connessione al database, e quindi il meglio che si possa fare in questa situazione è prendere in giro l'intera funzione e fidarsi delle suite di test di integrazione. Dipende anche da quanto sei disposto a progettare le tue classi per consentire che specifiche linee di codice vengano sostituite a scopo di test (come il suggerimento di Tobi del modello Method Method, che è buono perché permette "parolacce parziali" che esercitano metodi di una vera classe ignorando gli altri che hanno effetti collaterali).
Se il tuo modello di persistenza dei dati si basa sul codice nel tuo livello dati (come trigger, stored proc, ecc) allora semplicemente non c'è altro modo di esercitare il codice che stai scrivendo piuttosto che sviluppare test che vivono all'interno del livello dati o attraversare il confine tra il runtime dell'applicazione e il DBMS. Un purista direbbe che questo modello, per questo motivo, deve essere evitato a favore di qualcosa come un ORM. Non penso che andrei così lontano; anche nell'era delle query integrate nella lingua e di altre operazioni di persistenza controllate dal dominio e dipendenti dal dominio, vedo il valore nel bloccare il database solo per le operazioni esposte tramite stored procedure e, naturalmente, tali stored procedure devono essere verificate mediante l'automazione test. Ma tali test non sono test unit . Sono test integration .
Se hai un problema con questa distinzione, di solito si basa su una grande importanza posta sulla completa "copertura del codice" alias "copertura del test unitario". Volete assicurarvi che ogni riga del vostro codice sia coperta da un test unitario. Un nobile obiettivo sul suo volto, ma io dico sciocchezza; questa mentalità si presta a anti-modelli che vanno ben al di là di questo caso specifico, come la scrittura di test privi di asserzione che eseguono ma non esercitano il tuo codice. Questi tipi di end-run esclusivamente per motivi di numeri di copertura sono più dannosi che rilassare la copertura minima. Se vuoi assicurarti che ogni riga del tuo codebase sia eseguita da qualche test automatico, allora è facile; quando si calcolano le metriche di copertura del codice, includere i test di integrazione. Potresti anche fare un ulteriore passo avanti e isolare questi test "Itino" contestati ("Integrazione solo in nome"), e tra la tua suite di test unitaria e questa sottocategoria di test di integrazione (che dovrebbe comunque funzionare abbastanza velocemente) dovresti ottenere maledettamente vicino a una copertura completa.