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

21

Sto testando che una funzione fa ciò che ci si aspetta da una lista. Quindi voglio testare

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 un test per ogni caso, quindi avere
    • "WorksAsExpectedWhenNull"
    • "WorksAsExpectedWhenEmpty"
    • "WorksAsExpectedWhenSingleElement"
    • "WorksAsExpectedWhenMoreElements"
  • Un'altra scelta a cui non pensavo: -)
posta malarres 18.04.2018 - 12:29
fonte

5 risposte

29

La semplice regola empirica che uso per eseguire una serie di test in un caso di test, o molti, è la seguente: prevede solo una configurazione?

Quindi, se stavo testando che, per più elementi, entrambi li elaborassero tutti e derivassero il risultato corretto, potrei avere due o più asserzioni, ma devo solo impostare l'elenco una volta. Quindi un test case va bene.

Nel tuo caso, però, dovrei impostare un elenco nullo, una lista vuota 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 parametrizzato; vale a dire lo stesso caso di test viene eseguito su una varietà di dati di configurazione. La chiave per sapere se questa è una soluzione praticabile si trova nelle altre parti del test: "azione" e "asserire". Se è possibile eseguire le stesse azioni e asserire su ciascun set di dati, utilizzare questo approccio. Se ti trovi ad aggiungere if per esempio per eseguire codice diverso su parti diverse di tali dati, allora questa non è la soluzione. Utilizzare i singoli casi di test in quest'ultimo caso.

    
risposta data 18.04.2018 - 12:42
fonte
14

C'è un compromesso. Più impacchi in un test, più è probabile che avrai un effetto cipolla che cerca di farlo passare. In altre parole, il primo errore interrompe quel test. Non conoscerai le altre affermazioni finché non risolvi il primo errore. Detto questo, avere un gruppo di test unitari che sono per lo più simili ad eccezione del codice di impostazione è un lavoro 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, sia da un campo che da un metodo statico che genera i dati. Se alcuni dei tuoi fatti si applicano in base ad alcune precondizioni e altri no, questi framework introducono il concetto di un assunto . Il tuo Assume.that() salta semplicemente il test per i dati se fallisce la precondizione. Ciò ti consente di definire "Funziona come previsto" e quindi di fornire semplicemente molti dati. Quando visualizzi i risultati, hai una voce per i test principali e poi una sottovoce per ogni pezzo di dati.
  • Test parametrizzati . I test parametrizzati erano un precursore delle teorie, quindi potrebbe non esserci quella verifica preliminare che è possibile avere con le teorie. Il risultato finale è lo stesso. Tu vedi i risultati, hai una voce genitore per il test stesso, e poi una voce specifica per ogni punto di dati.
  • Un test con più asserzioni . Ci vuole meno tempo per fare il set up, ma finisci per scoprire i problemi un po 'alla volta. Se il test è troppo lungo e ci sono troppi scenari diversi testati, ci sono due grossi rischi: ci vorrà molto tempo per correre, e il tuo team ne sarà stufo e spegnerà il test.
  • Test multipli con un'implementazione simile . È importante notare che se le asserzioni sono diverse, i test non si sovrappongono. Tuttavia, questa sarebbe la saggezza convenzionale di un gruppo mirato al TDD.

Non sono del tutto rigoroso sul fatto che ci possa essere solo un'istruzione assert nel test, ma applico le restrizioni che tutti gli asseriti dovrebbero testare le post-condizioni di una singola azione. Se l'unica differenza tra i test sono i dati, sono dell'opinione mentale utilizzare le funzionalità di test basate sui dati più avanzate come test o teorie parametrizzati.

Pesate le vostre opzioni per decidere quale sia il miglior risultato. 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.

    
risposta data 18.04.2018 - 14:58
fonte
5

Si tratta di casi di test diversi, ma il codice per il test è lo stesso. L'utilizzo di test parametrizzati è quindi la soluzione migliore. Se il tuo framework di test non supporta la parametrizzazione, estrai il codice condiviso in una funzione helper e chiamalo da singoli casi di test.

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

Nel ciclo TDD red-green-refactor, è necessario aggiungere un set di dati di esempio alla volta. La combinazione di più test case in un test parametrizzato farebbe parte della fase di refactoring.

Un approccio piuttosto diverso è test di proprietà . Dovresti creare vari test (parametrizzati) che asseriscono varie proprietà della tua funzione, senza specificare i dati di input concreti. Per esempio. una proprietà potrebbe essere: per tutte le liste xs , la lista ys = f(xs) ha la stessa lunghezza di xs . La struttura di test genererebbe quindi elenchi interessanti e elenchi casuali e asserire che le tue proprietà valgono per tutti loro. Questo si allontana dalla specifica manuale degli esempi, poiché la scelta manuale degli esempi potrebbe non essere più interessante in casi limite.

    
risposta data 18.04.2018 - 13:11
fonte
3

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

Guarda questo post: Va bene avere più asserzioni in un singolo test di unità? . C'è anche una discussione pertinente e dettagliata:

My guideline is usually that you test one logical CONCEPT per test. you can have multiple asserts on the same object. they will usually be the same concept being tested. Source - Roy Osherove

[...]

Tests should fail for one reason only, but that doesn't always mean that there should be only one Assert statement. IMHO it is more important to hold to the "Arrange, Act, Assert" pattern.

The key is that you have only one action, and then you inspect the results of that action using asserts. But it is "Arrange, Act, Assert, End of test". If you are tempted to continue testing by performing another action and more asserts afterwards, make that a separate test instead. Source

    
risposta data 18.04.2018 - 13:15
fonte
0

Secondo me, dipende dalle condizioni del test.

  • Se il test ha solo 1 condizione per impostare il test, ma molti aspetti effetti. multi-assert è accettabile.
  • Ma quando hai più condizioni, significa che hai più test casi, ognuno dovrebbe essere coperto solo da 1 unità di test.
risposta data 19.04.2018 - 10:23
fonte

Leggi altre domande sui tag