Il test unitario a singola asserzione non viola il principio DRY?


12

Ogni volta che scrivo unit test ho sempre cercato di avere una singola asserzione per test per facilitare il debug quando i test falliscono. Tuttavia, quando seguo questa regola, mi sento come se stessi copiando costantemente lo stesso codice in ogni test e avendo più test diventa più difficile tornare a leggere e mantenere.

Quindi il test a singola asserzione viola il DRY?

E c'è una buona regola da seguire per trovare un buon equilibrio, come avere un solo test per metodo ? *

* Mi rendo conto che probabilmente non esiste una soluzione unica per questo, ma esiste un modo consigliato per affrontarlo?


5
È possibile estrarre il codice copiato in metodi
Ismail Badawi,

@IsmailBadawi sembra una buona idea. Suppongo che questi metodi dovrebbero restituire un'istanza dell'oggetto di classe che sto testando
Korey Hinton,

1
Stai creando qualcosa in un determinato stato da testare? Sembra un appuntamento fisso.
Dave Hillier,

@DaveHillier sì, ho imparato una nuova parola oggi. Grazie :)
Korey Hinton il

dipende da come interpreti 1 asserzione per test, se intendi una chiamata Assert *, allora sì, anche se vuoi assicurarti che gli invarianti siano ancora in attesa (poi di nuovo estrarli in un metodo), o se ci sono più effetti che puoi semplicemente " t test in una singola Assert (o se l'avete fatto, allora non sarebbe chiaro il motivo per cui non)
cricchetto maniaco del

Risposte:


14

I test unitari corretti hanno una convenzione di denominazione che consente di identificare immediatamente ciò che non ha funzionato:

public void AddNewCustomer_CustomerExists_ThrowsException()

Questo è il motivo per cui hai un'asserzione per test, in modo che ogni metodo (e il suo nome) corrispondano alla condizione che stai affermando.

Come hai giustamente sottolineato, ogni nuovo test avrà un codice di installazione simile. Come con qualsiasi codice, è possibile trasformare il codice comune nel proprio metodo per ridurre o eliminare la duplicazione e rendere il codice più ASCIUTTO. Alcuni framework di test sono appositamente progettati per consentire di inserire quel codice di installazione in un unico posto .

In TDD, nessun test è YAGNI, perché si scrivono test basati solo su ciò che si richiede al codice. Se non ti serve, non scriverai il test.


Il refactoring in un singolo metodo suona bene. Se avessi bisogno di un'istanza della classe in un certo stato, potrei creare e restituire una nuova istanza di quell'oggetto nel metodo refactored o come tu suggerisci di usare i metodi forniti dal framework di test per la configurazione iniziale del test.
Korey Hinton,

sul tuo ultimo punto, sono sicuro che avrei potuto scrivere i test per la funzionalità che avrei piace il codice per avere, piuttosto che la funzionalità di cui aveva bisogno
joelb

5

Quindi il test a singola asserzione viola il DRY?

No, ma promuove la violazione.

Detto questo, un buon design orientato agli oggetti tende a uscire dalla finestra per i test unitari, principalmente per buoni motivi. È più importante che i test unitari siano isolati l'uno dall'altro in modo che il test possa essere interrogato in modo isolato e, se necessario, corretto con la sicurezza di non interrompere altri test. Fondamentalmente che la correttezza e la leggibilità del test sono più importanti della sua dimensione o manutenibilità.

Francamente, non sono mai stato un fan di una sola affermazione per regola di test per i motivi che descrivi: porta a un sacco di codice della caldaia che è difficile da leggere, facile da usare in modo errato e difficile da correggere bene se si refacturing (che ti spinge a refactoring meno).

Se si suppone che una funzione restituisca un elenco di "pippo" e "barra" per un dato input, ma in qualsiasi ordine è perfettamente corretto usare due asserzioni per verificare che entrambi siano nel set di risultati. Il punto in cui ti trovi nei guai è quando un singolo test sta controllando due ingressi o due effetti collaterali e non sai quale dei due ha causato l'errore.

Lo considero una variazione del Principio della singola responsabilità: dovrebbe esserci solo una cosa che può far fallire un test, e in un mondo ideale quel cambiamento dovrebbe superare solo un test.

Ma alla fine è un compromesso. Hai più probabilità di dedicare più tempo a mantenere tutto il codice copia incolla, o passerai più tempo a cercare le cause alla radice quando i test potrebbero essere interrotti da più fonti. Fintanto che scrivi alcuni test, probabilmente non importa troppo. Nonostante il mio disprezzo per i test a singola asserzione, tendo a sbagliare sul lato di più test. Il tuo chilometraggio può variare.


1
Per i test, è più importante essere DAMP che DRY (frasi descrittive e significative).
Jörg W Mittag,

2

No. Questo sembra essere proprio il modo in cui lo stai facendo. A meno che tu non abbia trovato un riferimento notevole in cui sostengono che questa è una buona pratica.

Utilizzare un dispositivo di prova (sebbene nella terminologia XUnit l'insieme di prove, l'installazione e lo smontaggio sia l'apparecchiatura), ovvero alcune impostazioni o esempi che si applicano a tutti i test.

Usa metodi come faresti normalmente per strutturare il tuo codice. Quando si eseguono i test di refactoring, non si applica il solito TDD Red-Green-Refactor , ma si applica "Refactoring in the Red". Questo è,

  1. rompere deliberatamente il test,
  2. fai il tuo refactoring
  3. correggi il tuo test

In questo modo sai che i test danno ancora risultati positivi e negativi.

Esistono diversi formati standard per i test. Ad esempio, Disponi, Agisci, Assegna o Dato quando, quindi (BDD) . Prendi in considerazione l'utilizzo di una funzione separata per ogni passaggio. Dovresti essere in grado di chiamare la funzione per ridurre la piastra di cottura.

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.