Creazione di test unitari su un livello CRUD di un'applicazione, come posso rendere indipendenti i test?


14

Quindi sto cercando di rendere i miei test unitari il più precisi possibile, ma diventa problematico quando collaudo alcuni semplici metodi Aggiungi / Elimina.

Per il metodo add, devo fondamentalmente creare un oggetto fittizio e aggiungerlo, quindi dopo che il test ha esito positivo, devo eliminare l'oggetto fittizio.

E per il test di eliminazione, devo ovviamente creare un oggetto fittizio in modo da poterlo eliminare.

Come puoi vedere se un test fallisce, anche l'altro fallirà, poiché sono entrambi necessari.

Lo stesso con un sistema in cui avresti bisogno di scrivere un test che "Annulla un ordine" ... beh un po 'di ordine fittizio sarebbe necessario per annullare prima, questo non va contro le linee guida del test unitario?

Come devono essere gestiti casi come questo?


Potresti anche dare un'occhiata a questa domanda: programmers.stackexchange.com/questions/115455/…
Guven

Risposte:


13

Bene, non c'è niente di sbagliato in quello che stai facendo. Test multipli possono coprire lo stesso codice; significa solo che un problema farà fallire diversi test. Quello che vuoi evitare sono i test che dipendono dai risultati di altri test. Vale a dire, il test di eliminazione dipende dall'esecuzione del test di aggiunta e, quindi, se si esegue il test di eliminazione PRIMA del test di aggiunta, fallirà. Per evitare questo problema, assicurarsi di avere una "lavagna vuota" all'inizio di ogni test, in modo che ciò che accade in un determinato test non possa influenzare i test successivi.

Un ottimo modo per farlo è quello di eseguire i test su un database in memoria.

Nel test di aggiunta, crea un database vuoto, aggiungi l'oggetto e asserisci che è stato effettivamente aggiunto.

Nel test di eliminazione, crea il database con l'oggetto che eliminerai già al suo interno. Elimina l'oggetto e asserisci che è stato eliminato.

Soffiare via il database nel codice di abbattimento.


Il database in memoria è semplicemente veloce (in memoria) e semplice (in elaborazione). Puoi farlo con qualsiasi archivio dati.
Paul Draper,

3

Usa le transazioni.

Se si utilizza un database che supporta le transazioni, eseguire ogni test in una transazione. Ripristinare la transazione alla fine del test. Quindi ogni test lascerà invariato il database.

Sì, per annullare un ordine devi prima crearne uno. Va bene. Il test crea prima un ordine, quindi lo annulla, quindi verifica che l'ordine sia stato annullato.


Adoro questa idea. Lo ha implementato con grande effetto oggi.
pimbrouwers

3

Lo stai facendo bene. L'unico e unico principio fondamentale del test unitario è quello di coprire ogni percorso di codice che hai, quindi puoi essere sicuro che il tuo codice stia facendo quello che dovrebbe fare e continua a farlo dopo cambiamenti e refactoring. Mantenere i test unitari piccoli, semplici e monouso è un obiettivo utile, ma non è fondamentale. Avere un test che chiama due metodi correlati della tua API non è di per sé discutibile, infatti, come fai notare, è spesso necessario. Lo svantaggio di avere test ridondanti è solo che impiegano più tempo per scrivere, ma poiché quasi tutto in fase di sviluppo, questo è un compromesso che devi fare tutto il tempo e la soluzione migliore non è quasi mai uno dei punti estremi.


2

Le tecniche di test del software sono estremamente varie e più ti istruisci su di esse, inizierai a vedere molte indicazioni diverse (e talvolta contrastanti). Non esiste un unico "libro" da seguire.

Penso che ti trovi in ​​una situazione in cui hai visto alcune indicazioni per i test unitari che dicono cose del genere

  • Ogni test dovrebbe essere autonomo e non essere influenzato da altri test
  • Ogni unit test dovrebbe testare una cosa e solo una cosa
  • I test unitari non dovrebbero colpire il database

e così via. E tutti quelli hanno ragione, a seconda di come si definisce 'unit test' .

Definirei un "test unitario" come qualcosa del tipo: "un test che esercita una parte di funzionalità per un'unità di codice, isolata da altri componenti dipendenti".

In base a tale definizione, ciò che si sta facendo (se è necessario aggiungere un record a un database prima di poter eseguire il test) non è affatto un "test unitario", ma più di quello che comunemente viene chiamato un "test di integrazione". (Un vero test unitario, per mia definizione, non colpirà il database, quindi non sarà necessario aggiungere un record prima di eliminarlo.)

Un test di integrazione eserciterà funzionalità che utilizzano più componenti (come un'interfaccia utente e un database) e la guida che si applicherebbe ai test unitari non si applica necessariamente ai test di integrazione.

Come altri hanno già detto nelle loro risposte, ciò che stai facendo non è necessariamente sbagliato anche se fai cose contrarie ad alcune indicazioni del test unitario. Invece, prova a ragionare su ciò che stai realmente testando in ciascun metodo di test e se trovi che hai bisogno di più componenti per soddisfare il tuo test e alcuni componenti richiedono una pre-configurazione, vai avanti e fallo.

Ma soprattutto, capisci che esistono molti tipi di test software (unit test, test di sistema, test di integrazione, test esplorativi, ecc.) E non provare ad applicare la guida di un tipo a tutti gli altri.


Quindi stai dicendo che non puoi eliminare unit test dal database?
ChrisF

Se stai colpendo il database, è (per definizione) un test di integrazione, non un test unitario. Quindi, in questo senso, no. Non è possibile 'unit test' eliminazione da un database. Quello che puoi testare l'unità è che quando viene richiesto di eliminare alcuni dati dal codice che stai testando, interagisce correttamente con il modulo di accesso ai dati.
Eric King,

Ma il punto è che alcune persone possono definire 'unit test' in modo diverso, quindi dobbiamo stare attenti quando applichiamo la guida 'unit test', poiché la guida potrebbe non applicarsi come pensiamo.
Eric King,

1

Questo è esattamente il motivo per cui una delle altre linee guida è l'uso delle interfacce. Se il metodo accetta un oggetto che implementa un'interfaccia anziché un'implementazione di classe specifica, è possibile creare una classe che non dipende dal resto della base di codice.

Un'altra alternativa è utilizzare un framework beffardo. Ciò consente di creare facilmente questo tipo di oggetti fittizi che possono essere passati al metodo che si sta testando. È possibile che sia necessario creare alcune implementazioni di stub per la classe fittizia, ma crea comunque una separazione dall'implementazione effettiva e da ciò che riguarda il test.


1

Come puoi vedere se un test fallisce, anche l'altro fallirà, poiché sono entrambi necessari.

Così?

... questo non va contro le linee guida del test unitario?

No.

Come devono essere gestiti casi come questo?

Diversi test possono essere indipendenti e tutti falliscono a causa dello stesso bug. Questo è in realtà normale. Molti test possono - indirettamente - testare alcune funzionalità comuni. E tutti falliscono quando si rompono le funzionalità comuni. Niente di sbagliato.

I test unitari sono definiti esattamente come classi in modo da poter condividere facilmente il codice, come un comune record fittizio utilizzato per testare l'aggiornamento e l'eliminazione.


1

È possibile utilizzare un framework simulato o un "ambiente" con un database in memoria. L'ultima è una classe in cui è possibile creare tutto il necessario per far passare il test, prima dell'esecuzione del test.

Preferisco l'ultimo: gli utenti possono aiutarti a inserire alcuni dati in modo che i tuoi test diventino più vicini al mondo reale.


Vero - ma in realtà non stai testando la vera connessione al database qui. A meno che non si presuma che funzioni sempre, ma le ipotesi sono pericolose.
ChrisF
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.