C'è, da qualche parte nel tuo codebase, una riga di codice che esegue l'effettiva azione di connessione al DB remoto. Questa riga di codice è, 9 volte su 10, una chiamata a un metodo "incorporato" fornito dalle librerie di runtime specifiche per la tua lingua e il tuo ambiente. In quanto tale, non è il "tuo" codice e quindi non è necessario testarlo; ai fini di un unit test, ci si può fidare che questa chiamata al metodo funzionerà correttamente. Quello che puoi e dovresti ancora testare nella tua suite di test di unità sono cose come garantire che i parametri che verranno utilizzati per questa chiamata siano quelli che ti aspetti che siano, come assicurarti che la stringa di connessione sia corretta, o l'istruzione SQL o nome della procedura memorizzata.
Questo è uno degli scopi dietro la restrizione che i test unitari non dovrebbero lasciare il loro "sandbox" di runtime ed essere dipendenti dallo stato esterno. In realtà è abbastanza pratico; lo scopo di un unit test è verificare che il codice che hai scritto (o che stai per scrivere, in TDD) si comporti come pensavi. Il codice che non hai scritto, come la libreria che stai utilizzando per eseguire le operazioni del database, non dovrebbe far parte dell'ambito di nessun test unitario, per la semplice ragione che non l'hai scritto.
Nella tua suite di test di integrazione , queste restrizioni sono attenuate. Adesso puoitest di progettazione che tocchino il database, per assicurarsi 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 quanto più velocemente viene eseguita (in modo da poter verificare rapidamente che tutte le affermazioni 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 da risorse esterne. Lascia che il build-bot gestisca l'esecuzione della tua suite di integrazione completa ogni poche ore, eseguendo i test che bloccano le risorse esterne, in modo che gli sviluppatori non si calpestino reciprocamente eseguendo questi stessi test localmente. E se la build si rompe, e allora? Viene data molta più importanza a 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 strategia esatta 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 metodo integrate e altro codice che dipende dall'avere un connessione al database, quindi il meglio che puoi fare in questa situazione è deridere l'intera funzione e fidarti delle tue suite di test di integrazione. Dipende anche da quanto sei disposto a progettare le tue classi per consentire la sostituzione di specifiche righe di codice a scopo di test (come il suggerimento di Tobi sul modello Metodo del modello, che è buono perché consente "derisioni parziali"
Se il tuo modello di persistenza dei dati si basa sul codice nel tuo livello dati (come trigger, processi memorizzati, ecc.), Semplicemente non c'è altro modo per esercitare il codice che stai scrivendo se non quello di sviluppare test che vivono all'interno del livello dati o attraversano 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 di andare così lontano; anche nell'era delle query integrate nella lingua e di altre operazioni di persistenza controllate dal compilatore e dipendenti dal dominio, vedo il valore di bloccare il database solo alle operazioni esposte tramite stored procedure e, naturalmente, tali procedure archiviate devono essere verificate utilizzando test. Ma tali test non sono test unitari . Sono integrazione test.
Se hai un problema con questa distinzione, di solito si basa su una grande importanza posta sulla "copertura del codice" completa, nota anche come "copertura del test unitario". Vuoi assicurarti che ogni riga del tuo codice sia coperta da un unit test. Un obiettivo nobile in faccia, ma dico lavanda; quella mentalità si presta ad anti-pattern che vanno ben oltre questo caso specifico, come scrivere test senza asserzioni che eseguono ma non si esercitanoil 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 della tua base di codice venga eseguita da un 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 contestati test "Itino" ("Integrazione solo nel nome"), e tra la tua suite di test unitari e questa sottocategoria di test di integrazione (che dovrebbe comunque essere eseguita ragionevolmente velocemente) dovresti ottenere dannazione quasi vicino alla piena copertura.