Test di un elenco ... Tutti nello stesso test o un test per ogni condizione?


21

Sto testando che una funzione fa quello che ci si aspetta da un elenco. Quindi voglio provare

f(null) -> null
f(empty) -> empty
f(list with one element) -> list with one element
f(list with 2+ elements) -> list with the same number of elements, doing what expected

Per fare ciò, qual è l'approccio migliore?

  • Test di tutti i casi nello stesso test (metodo), sotto il nome "WorksAsExpected"
  • Effettuare una prova per ogni caso, avendo così
    • "WorksAsExpectedWhenNull"
    • "WorksAsExpectedWhenEmpty"
    • "WorksAsExpectedWhenSingleElement"
    • "WorksAsExpectedWhenMoreElements"
  • Un'altra scelta a cui non stavo pensando :-)


2
Vorrei scrivere quelli come casi di test separati. È possibile utilizzare test con parametri se il sistema di test lo supporta.
jonrsharpe,

5
Se scrivi i tuoi test in un dato ... Quando ... Allora lo stile allora diventa evidente che dovrebbero davvero essere testati separatamente ...
Robbie Dee,

1
Vorrei solo aggiungere: IMO, è bene separare i casi limite (come null e vuoto) in test separati, perché spesso comportano una logica del caso speciale in diverse possibili implementazioni e se questi test falliscono, indicano chiaramente in in che modo il codice sottoposto a test fallisce (non sarà necessario scavare più a fondo o eseguire il debug del test case per capire cosa sta succedendo).
Filip Milovanović,

1
Elenco con elementi duplicati?
atayenel,

Risposte:


30

La semplice regola empirica che utilizzo per eseguire una serie di test in uno o più casi è: comporta solo una configurazione?

Quindi, se stavo testando che, per più elementi, entrambi hanno elaborato tutti e ottenuto il risultato corretto, potrei avere due o più asserzioni, ma devo impostare l'elenco solo una volta. Quindi un caso di prova va bene.

Nel tuo caso, però, dovrei impostare un elenco nullo, un elenco vuoto, ecc. Sono più configurazioni. Quindi in questo caso creerei sicuramente più test.

Come altri hanno già detto, quei "test multipli" potrebbero essere in grado di esistere come un singolo caso di test con parametri; cioè lo stesso caso di test viene eseguito su una varietà di dati di configurazione. La chiave per sapere se questa è una soluzione praticabile sta nelle altre parti del test: "azione" e "asserire". Se è possibile eseguire le stesse azioni e le stesse asserzioni su ciascun set di dati, utilizzare questo approccio. Se ti ritrovi ad aggiungere if, ad esempio, per eseguire codice diverso su parti diverse di quei dati, questa non è la soluzione. Utilizzare casi di test individuali in quest'ultimo caso.


14

C'è un compromesso. Più impacchetti in un test, più è probabile che tu abbia un effetto cipolla che prova a farlo passare. In altre parole, il primo fallimento interrompe quel test. Non conoscerai le altre asserzioni fino a quando non risolverai il primo errore. Detto questo, avere un sacco di unit test che sono per lo più simili, ad eccezione del codice di configurazione, è molto impegnativo solo per scoprire che alcuni funzionano come scritti e altri no.

Possibili strumenti, basati sul tuo framework:

  • Teorie . Una teoria ti consente di testare una serie di fatti su un insieme di dati. Il framework alimenterà quindi i test con più scenari di dati di test, mediante un campo o un metodo statico che genera i dati. Se alcuni dei tuoi fatti si applicano sulla base di alcuni presupposti e altri, questi quadri non introducono il concetto di assunzione . Il tuoAssume.that() salta semplicemente il test per i dati se fallisce la precondizione. Ciò ti consente di definire "Funziona come previsto" e quindi di inviargli semplicemente molti dati. Quando si visualizzano i risultati, si dispone di una voce per i test padre e quindi di una voce secondaria per ogni dato.
  • Test con parametri . I test con parametri erano un precursore delle teorie, quindi potrebbe non esserci quel controllo preliminare che puoi avere con le teorie. Il risultato finale è lo stesso. Si visualizzano i risultati, si dispone di una voce principale per il test stesso e quindi una voce specifica per ciascun punto dati.
  • Un test con più asserzioni . Ci vuole meno tempo per eseguire l'installazione, ma alla fine si scoprono problemi un po 'alla volta. Se il test è troppo lungo e ci sono troppi scenari diversi testati, ci sono due grandi rischi: ci vorrà molto tempo per correre e il tuo team si stancherà di farlo e spegnerà il test.
  • Test multipli con implementazione simile . È importante notare che se le asserzioni sono diverse, i test non si sovrappongono. Tuttavia, questa sarebbe la saggezza convenzionale di un team focalizzato sul TDD.

Non sono della rigorosa mentalità secondo assertcui nel tuo test può esserci solo un'istruzione, ma metto le restrizioni secondo cui tutti gli assertori dovrebbero testare le condizioni post di una singola azione. Se l'unica differenza tra i test è data, sono dell'idea di utilizzare le funzionalità di test basate sui dati più avanzate come test con parametri o teorie.

Pesare le opzioni per decidere quale sia il risultato migliore. Dirò che "WorksAsExpectedWhenNull" è fondamentalmente diverso rispetto a tutti i casi in cui si ha a che fare con una raccolta che ha un numero variabile di elementi.


5

Questi sono casi di test diversi, ma il codice per il test è lo stesso. L'uso di test con parametri è quindi la soluzione migliore. Se il framework di test non supporta la parametrizzazione, estrarre il codice condiviso in una funzione di supporto e chiamarlo dai singoli casi di test.

Cerca di evitare la parametrizzazione tramite un loop all'interno di un caso di test, poiché ciò rende difficile determinare quale set di dati ha causato l'errore.

Nel ciclo TDD rosso-verde-refactor, è necessario aggiungere un set di dati di esempio alla volta. La combinazione di più casi di test in un test parametrizzato sarebbe parte della fase di refactoring.

Un approccio piuttosto diverso è il test delle proprietà . Dovresti creare vari test (parametrizzati) che affermano varie proprietà della tua funzione, senza specificare dati concreti di input. Ad esempio una proprietà potrebbe essere: per tutti gli elenchi xs, l'elenco ys = f(xs)ha la stessa lunghezza di xs. Il framework di test genererebbe quindi elenchi interessanti ed elenchi casuali e affermerebbe che le tue proprietà valgono per tutti loro. Questo si allontana dalla specificazione manuale degli esempi, poiché la scelta manuale degli esempi potrebbe perdere casi limite interessanti.


Il "miss" nell'ultima frase non dovrebbe essere "find"?
Robbie Dee,

@RobbieDee L'inglese è ambiguo, risolto.
amon

3

Avere un test per ogni caso è appropriato perché testare un singolo concetto in ogni test è una buona linea guida che è spesso raccomandata.

Vedi questo post: è corretto avere più assert in un singolo test unitario? . C'è una discussione pertinente e dettagliata anche lì:

La mia linea guida è di solito che testi un CONCEPT logico per test. puoi avere più asserzioni sullo stesso oggetto. di solito saranno lo stesso concetto in fase di test. Fonte: Roy Osherove

[...]

I test dovrebbero fallire per un solo motivo, ma ciò non significa sempre che ci dovrebbe essere solo un'istruzione Assert. IMHO è più importante attenersi al modello "Disponi, agisci, asserisci".

La chiave è che hai una sola azione e quindi controlli i risultati di quell'azione usando gli assert. Ma è "Disponi, agisci, asserisci, fine del test". Se sei tentato di continuare il test eseguendo un'altra azione e più affermazioni in seguito, esegui invece un test separato. fonte


0

Secondo me dipende dalle condizioni del test.

  • Se il test ha solo 1 condizione per impostare il test, ma molti effetti collaterali. multi-assert è accettabile.
  • Ma quando hai più condizioni, significa che hai più casi di test, ognuno dovrebbe essere coperto da un solo test unitario.

questo sembra più un commento, vedi Come rispondere
moscerino
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.