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

11

Quindi sto provando a rendere il mio unit test il più possibile da manuale, ma diventa problematico quando sto testando alcuni semplici metodi di aggiunta / eliminazione.

Per il metodo add, in pratica devo creare un oggetto fittizio e aggiungerlo, quindi dopo che il test ha avuto successo, devo cancellare l'oggetto fittizio.

E per il test di cancellazione, ovviamente devo creare un oggetto fittizio in modo che io possa cancellarlo.

Come puoi vedere se un test fallisce, anche l'altro fallirà, dato che sono entrambi abbastanza necessari.

Stessa cosa con un sistema in cui dovresti scrivere un test che "annulla un ordine" ... beh, sarebbe necessario qualche ordine fittizio per cancellare prima, non va contro le linee guida del test unitario?

In che modo casi come questo dovevano essere gestiti?

    
posta Kyle 22.02.2012 - 21:13
fonte

7 risposte

11

Bene, non c'è niente di sbagliato in quello che stai facendo. Test multipli possono coprire lo stesso codice; significa solo che un problema causerà il fallimento di diversi test. Quello che vuoi evitare sono i test che dipendono dai risultati di altri test. Ad esempio, il test di eliminazione dipende dal test di aggiunta eseguito, e quindi se si esegue il test di eliminazione PRIMA del test di aggiunta, fallirà. Per evitare questo problema, assicurati di avere una "lavagna vuota" all'inizio di ciascun test, in modo che ciò che accade in un dato test non possa influenzare i test successivi.

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

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

Nel test di cancellazione, crea il database con l'oggetto che stai già eliminando. Elimina l'oggetto e asserisci che è stato eliminato.

Elimina il database nel tuo codice di rimozione.

    
risposta data 22.02.2012 - 21:28
fonte
2

Lo stai facendo bene. L'unico e fondamentale principio di unit test è di coprire ogni percorso di codice che hai, quindi puoi essere sicuro che il tuo codice sta facendo quello che dovrebbe fare e continua a farlo dopo modifiche e refactoring. Mantenere i test unitari piccoli, semplici e monouso è un obiettivo utile, ma non è fondamentale. Avere un test che chiami due metodi correlati della propria API non è di per sé discutibile, infatti, come si fa notare, è spesso necessario. Lo svantaggio di avere test ridondanti è solo che richiedono più tempo per scrivere, ma come quasi tutto nello sviluppo, questo è un compromesso che devi fare sempre e la soluzione migliore non è quasi mai uno dei punti estremi.

    
risposta data 22.02.2012 - 21:37
fonte
2

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

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

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

e così via. E tutti questi sono giusti, a seconda di come si definisce 'unit test' .

Definirei un 'unit test' come qualcosa di simile: "un test che esercita un pezzo di funzionalità per una unità di codice, isolata da altre componenti dipendenti".

Sotto questa definizione, ciò che stai 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 viene comunemente chiamato "test di integrazione" . (Un vero test unitario, secondo la 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 necessariamente applicare ai test di integrazione.

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

Ma soprattutto, capisci che ci sono molti tipi di test del software (test unitari, test di sistema, test di integrazione, test esplorativi, ecc.), e non provare ad applicare la guida di un tipo a tutti i altri.

    
risposta data 22.02.2012 - 23:27
fonte
1

Questo è esattamente il motivo per cui una delle altre linee guida è quella di utilizzare le interfacce. Se il metodo accetta un oggetto che implementa un'interfaccia invece di un'implementazione di classe specifica, puoi creare una classe che non dipende dal resto della base di codice.

Un'altra alternativa è usare una struttura di derisione. Questi ti permettono di creare facilmente questo tipo di oggetti fittizi che possono essere passati al metodo che stai testando. È possibile che sia necessario creare alcune implementazioni di stub per la classe dummy, ma crea comunque una separazione dall'implementazione effettiva e da ciò che riguarda il test.

    
risposta data 22.02.2012 - 21:26
fonte
1

Utilizza le transazioni.

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

Sì, per annullare un ordine devi prima crearne uno. Va bene. Il test creerà prima un ordine, quindi lo annullerà, quindi verificherà che l'ordine sia stato annullato.

    
risposta data 22.02.2012 - 21:28
fonte
1

As you can see if one test fails, the other will fail too, as they're both kinda needed.

E allora?

... doesn't this go against the guidelines of unit testing?

No.

How are cases like this meant to be handled?

Diversi test possono essere indipendenti e tutti falliscono a causa dello stesso bug. Questo è in realtà normale. Un sacco di test possono - indirettamente - testare alcune funzionalità comuni. E tutti falliscono quando la funzionalità comune è rotta. Niente di male.

I test unitari sono definiti come classi precisamente in modo che possano condividere facilmente il codice, come un comune fascicolo fittizio utilizzato per testare l'aggiornamento e l'eliminazione.

    
risposta data 22.02.2012 - 21:39
fonte
1

È possibile utilizzare un framework di simulazione o utilizzare un "ambiente" con un database in memoria. L'ultimo è un corso in cui è possibile creare tutto ciò che serve per superare il test, prima dell'esecuzione del test.

Preferisco l'ultimo - gli utenti possono aiutarti a inserire alcuni dati in modo che i test si avvicinino al mondo reale.

    
risposta data 22.02.2012 - 21:44
fonte

Leggi altre domande sui tag