Come posso testare il codice relativo al database con NUnit?

Voglio scrivere test unitari con NUnit che colpiscono il database. Mi piacerebbe avere il database in uno stato coerente per ogni test. Ho pensato che le transazioni mi avrebbero permesso di “annullare” ogni test, quindi ho cercato in giro e ho trovato diversi articoli dal 2004-05 sull’argomento:

  • http://weblogs.asp.net/rosherove/archive/2004/07/12/180189.aspx
  • http://weblogs.asp.net/rosherove/archive/2004/10/05/238201.aspx
  • http://davidhayden.com/blog/dave/archive/2004/07/12/365.aspx
  • http://haacked.com/archive/2005/12/28/11377.aspx

Questi sembrano risolvere l’implementazione di un attributo personalizzato per NUnit che si basa sulla possibilità di eseguire il rollback delle operazioni DB dopo l’esecuzione di ciascun test.

È fantastico ma …

  1. Questa funzionalità esiste da qualche parte in NUnit in modo nativo?
  2. Questa tecnica è stata migliorata negli ultimi 4 anni?
  3. Questo è ancora il modo migliore per testare il codice relativo al database?

Modifica: non è che voglio testare il mio DAL in particolare, è più che voglio testare pezzi del mio codice che interagiscono con il database. Perché questi test siano “no-touch” e ripetibili, sarebbe fantastico se potessi resettare il database dopo ognuno di essi.

Inoltre, voglio semplificare questo aspetto in un progetto esistente che al momento non ha luogo di test. Per questo motivo, non posso praticamente creare script di un database e dati da zero per ogni test.

NUnit ora ha un attributo [Rollback], ma preferisco farlo in un modo diverso. Io uso la class TransactionScope . Ci sono un paio di modi per usarlo.

[Test] public void YourTest() { using (TransactionScope scope = new TransactionScope()) { // your test code here } } 

Poiché non hai comunicato a TransactionScope di eseguire il commit, il rollback verrà eseguito automaticamente. Funziona anche se un’asserzione fallisce o viene lanciata qualche altra eccezione.

L’altro modo è utilizzare [SetUp] per creare TransactionScope e [TearDown] per chiamare Dispose su di esso. Elimina la duplicazione del codice, ma realizza la stessa cosa.

 [TestFixture] public class YourFixture { private TransactionScope scope; [SetUp] public void SetUp() { scope = new TransactionScope(); } [TearDown] public void TearDown() { scope.Dispose(); } [Test] public void YourTest() { // your test code here } } 

Questo è sicuro quanto l’istruzione using in un singolo test perché NUnit garantisce che TearDown venga chiamato.

Detto tutto ciò, penso che i test che colpiscono il database non siano realmente test unitari. Li scrivo ancora, ma li considero test di integrazione. Li vedo ancora come un valore. Un posto in cui li uso spesso è nel test LINQ al codice SQL. Non uso il designer. Scrivo a mano DTO e attributi. Sono stato conosciuto per sbagliare. I test di integrazione aiutano a cogliere il mio errore.

Sono appena andato a un gruppo di utenti .NET e il presentatore ha detto che ha usato SQLlite in configurazione di test e teardown e ha utilizzato l’opzione in memoria. Doveva fondere la connessione un po ‘ed esplicitamente distruggere la connessione, ma ogni volta avrebbe dato un DB pulito.

http://houseofbilz.com/archive/2008/11/14/update-for-the-activerecord-quotmockquot-framework.aspx

Per questo tipo di test, ho sperimentato con NDbUnit (lavorando in concerto con NUnit). Se la memoria serve, era una porta di DbUnit dalla piattaforma Java. Aveva un sacco di comandi chiari per il tipo di cosa che stai cercando di fare. Il progetto sembra essersi trasferito qui:

http://code.google.com/p/ndbunit/

(era usato su http://ndbunit.org ).

La fonte sembra essere disponibile tramite questo link: http://ndbunit.googlecode.com/svn/trunk/

Chiamerei questi test di integrazione, ma non importa. Quello che ho fatto per questi test è che i miei metodi di impostazione nella class di test cancellano tutte le tabelle di interesse prima di ogni test. In generale, scrivo SQL per farlo in modo che non stia usando le classi sotto test.

In genere, faccio affidamento su un ORM per il mio datalayer e quindi non scrivo per molti test di unità. Non sento il bisogno di un codice di test unitario che non scrivo. Per il codice che aggiungo nel layer, generalmente utilizzo l’injection dependency per astrarre la connessione effettiva al database in modo che quando eseguo il test del codice, non tocchi il database effettivo. Fatelo in combinazione con una struttura di derisione per ottenere i migliori risultati.

Prendi in considerazione la possibilità di creare uno script di database in modo da poterlo eseguire automaticamente da NUnit e manualmente per altri tipi di test. Ad esempio, se si utilizza Oracle, avviare SqlPlus da NUnit ed eseguire gli script. Questi script sono in genere più veloci da scrivere e più facili da leggere. Inoltre, molto importante, l’esecuzione di SQL da Toad o equivalenti è più illuminante che eseguire SQL dal codice o passare attraverso un ORM dal codice. Generalmente creerò sia uno script di setup che di teardown e li metto nei metodi di setup e teardown.

Un’altra questione è se si debba passare dal DB a tutti i test unitari. Credo che spesso abbia senso farlo. Per molte app il database è il centro assoluto dell’azione, la logica è fortemente basata su impostazioni e tutte le altre tecnologie e linguaggi e tecniche stanno passando i fantasmi. E con l’avvento dei linguaggi funzionali stiamo iniziando a capire che SQL, come JavaScript, è in realtà un grande linguaggio che è stato proprio lì sotto i nostri nasi in tutti questi anni.

Come per inciso, Linq to SQL (che a me non piace mai nel concetto, però, non ho mai usato) mi sembra quasi un modo per fare raw SQL da codice senza ammettere quello che stiamo facendo. Ad alcune persone piace SQL e sanno che gli piace, ad altri piace e non sanno che gli piace. 🙂