Come scrivere test di unità "buoni"?

61

Attivato da questo thread , io (di nuovo) sto pensando di usare finalmente i test unitari nei miei progetti. Alcuni poster dicono qualcosa come "I test sono fantastici, se sono buoni test". La mia domanda ora: quali sono i "buoni" test?

Nelle mie applicazioni, la parte principale è spesso una sorta di analisi numerica, a seconda di grandi quantità di dati osservati, e risultante in una funzione di adattamento che può essere utilizzata per modellare questi dati. Ho trovato particolarmente difficile costruire test per questi metodi, dal momento che il numero di possibili input e risultati è troppo grande per testare solo ogni caso, e i metodi stessi sono spesso abbastanza lunghi e non possono essere facilmente refactoring senza sacrificare le prestazioni. Sono particolarmente interessato ai test "buoni" per questo tipo di metodo.

    
posta Jens 24.11.2010 - 10:47
fonte

9 risposte

53

L'Art of Unit Testing riporta quanto segue sui test unitari:

A unit test should have the following properties:

  • It should be automated and repeatable.
  • It should be easy to implement.
  • Once it’s written, it should remain for future use.
  • Anyone should be able to run it.
  • It should run at the push of a button.
  • It should run quickly.

e successivamente aggiunge che dovrebbe essere completamente automatizzato, affidabile, leggibile e manutenibile.

Consiglio vivamente di leggere questo libro se non lo hai già fatto.

A mio parere, tutti questi sono molto importanti, ma gli ultimi tre (affidabili, leggibili e mantenibili) in particolare, come se i test avessero queste tre proprietà, di solito il tuo codice li ha anche.

    
risposta data 24.11.2010 - 14:58
fonte
42

Un buon test unitario non rispecchia la funzione che sta testando.

Come esempio molto semplificato, considera di avere una funzione che restituisce una media di due int. Il test più completo chiamerebbe la funzione e controllerebbe se un risultato è in realtà una media. Questo non ha alcun senso: si sta replicando (replicando) la funzionalità che si sta testando. Se hai commesso un errore nella funzione principale, farai lo stesso errore nel test.

In altre parole, se ti ritrovi a replicare la funzionalità principale nel test dell'unità, è probabile che stia perdendo tempo.

    
risposta data 24.11.2010 - 11:28
fonte
10

I buoni test unitari sono essenzialmente le specifiche in forma eseguibile:

  1. descrive il comportamento del codice corrispondente ai casi d'uso
  2. copre i casi dell'angolo tecnico (cosa succede se viene passato un valore nullo) - se un test non è presente per un caso d'angolo, il comportamento non è definito.
  3. interrompi se il codice testato cambia rispetto alle specifiche

Ho trovato Test-Driven-Development molto adatto per le routine di libreria in quanto essenzialmente si scrive prima l'API e POI l'effettiva implementazione.

    
risposta data 24.11.2010 - 11:23
fonte
7

per TDD, test "buoni" test funzionalità che il cliente desidera ; le funzionalità non corrispondono necessariamente alle funzioni e gli scenari di test non devono essere creati dallo sviluppatore nel vuoto

nel tuo caso - sto indovinando - la 'caratteristica' è che la funzione di adattamento modella i dati di input entro una certa tolleranza di errore. Dato che non ho idea di cosa stai facendo davvero, sto inventando qualcosa; speriamo che sia analgico.

Esempio di storia:

As a [X-Wing Pilot] I want [no more than 0.0001% fit error] so that [the targeting computer can hit the Death Star's exhaust port when moving at full speed through a box canyon]

Quindi vai a parlare con i piloti (e con il computer di destinazione, se senziente). Per prima cosa parli di ciò che è "normale", quindi parla dell'anormale. Scopri cosa conta veramente in questo scenario, cosa è comune, cosa è improbabile e cosa è semplicemente possibile.

Diciamo che normalmente avrai una finestra di mezzo secondo su sette canali di dati di telemetria: velocità, intonazione, rotazione, imbardata, vettore bersaglio, dimensione obiettivo e velocità bersaglio, e che questi valori saranno costanti o mutevoli linearmente. Anormalmente potresti avere meno canali e / o i valori potrebbero cambiare rapidamente. Quindi insieme ti vengono proposti alcuni test come:

//Scenario 1 - can you hit the side of a barn?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is zero
    and all other values are constant,
Then:
    the error coefficient must be zero

//Scenario 2 - can you hit a turtle?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is less than c
    and all other values are constant,
Then:
    the error coefficient must be less than 0.0000000001/ns

...

//Scenario 42 - death blossom
Given:
    all 7 channels with 30% dropout and a 0.05 second sampling window
When:
    speed is zero
    and position is within enemy cluster
    and all targets are stationary
Then:
    the error coefficient must be less than 0.000001/ns for each target

Ora, potresti aver notato che non esiste uno scenario per la particolare situazione descritta nella storia. Risulta, dopo aver parlato con il cliente e gli altri stakeholder, che l'obiettivo nella storia originale era solo un esempio ipotetico. I veri test sono usciti dalla discussione successiva. Questo può succedere La storia dovrebbe essere riscritta, ma non deve essere [poiché la trama è solo un segnaposto per una conversazione con il cliente].

    
risposta data 24.11.2010 - 16:28
fonte
5

Crea test per casi d'angolo, come un set di test contenente solo il numero minimo di input (possibile 1 o 0) e alcuni casi standard. Quei test unitari non sostituiscono i test di accettazione approfonditi, né dovrebbero esserlo.

    
risposta data 24.11.2010 - 11:00
fonte
5

Ho visto molti casi in cui le persone investono una quantità enorme di sforzi nella scrittura di test per il codice che viene inserito raramente e non scrivono test per il codice immesso frequentemente.

Prima di sederti per scrivere qualsiasi test, dovresti esaminare una specie di grafico di chiamata, per assicurarti di pianificare una copertura adeguata.

Inoltre, non credo nelle prove di scrittura solo per il gusto di dire "Sì, lo testiamo". Se sto usando una libreria che è stata abbandonata e rimarrà immutabile, non ho intenzione di sprecare un giorno a scrivere test per assicurarmi che l'interno di un'API che non cambierà mai funzioni come previsto, anche se alcune parti di esso segnano in alto su un grafico di chiamata. Prova che consuma la libreria (il mio codice) lo mette in evidenza.

    
risposta data 24.11.2010 - 11:49
fonte
4

Non proprio così TDD, ma dopo aver approfondito il QA puoi migliorare i test impostando casi di test per riprodurre eventuali bug che si verificano durante il processo di controllo qualità. Questo può essere particolarmente utile quando si entra nel supporto a lungo termine e si inizia ad andare in un posto dove si rischia che la gente reintroduca inavvertitamente vecchi bug. Avere un test in atto per catturarlo è particolarmente prezioso.

    
risposta data 24.11.2010 - 12:09
fonte
3

Cerco di fare in modo che ogni test provi solo una cosa. Provo a dare ad ogni test un nome come shouldDoSomething (). Provo a testare il comportamento, non l'implementazione. Ho solo provato i metodi pubblici.

Di solito ho uno o alcuni test per il successo, e quindi forse una manciata di test per il fallimento, per metodo pubblico.

Uso molto i mock-up. Un buon mock-framework sarebbe probabilmente molto utile, come PowerMock. Anche se non sto ancora usando.

Se la classe A usa un'altra classe B, aggiungerei un'interfaccia, X, in modo che A non usi direttamente B. Quindi creerei il mockup XMockup e lo userei al posto di B nei miei test. Aiuta davvero ad accelerare l'esecuzione del test, a ridurre la complessità del test, e riduce anche il numero di test che scrivo per A poiché non devo far fronte alle peculiarità di B. Posso ad esempio provare che A chiama X.someMethod () invece di un effetto collaterale di chiamare B.someMethod ().

Tieni anche il codice di prova pulito.

Quando si utilizza un'API, come un livello di database, lo prenderei in giro e abilitare il mock-up per generare un'eccezione a ogni possibilità possibile su comando. Quindi eseguo i test uno senza lanciare e il ciclo in loop, ogni volta lanciando un'eccezione alla prossima opportunità finché il test non supera nuovamente. Un po 'come i test di memoria disponibili per Symbian.

    
risposta data 24.11.2010 - 12:43
fonte
2

Vedo che Andry Lowry ha già pubblicato le metriche dei test unitari di Roy Osherove; ma sembra che nessuno abbia presentato il set (gratuito) che lo Zio Bob fornisce in Clean Code (132-133). Usa l'acronimo FIRST (qui con i miei riepiloghi):

  • Veloce (dovrebbero essere eseguiti rapidamente, quindi alle persone non dispiacerà eseguirli)
  • Indipendente (i test non dovrebbero eseguire il setup o il teardown l'uno dell'altro)
  • Ripetibile (dovrebbe essere eseguito su tutti gli ambienti / piattaforme)
  • Auto-validazione (completamente automatizzato, l'output deve essere "pass" o "fail", non un file di registro)
  • Tempestivi (quando scriverli, prima di scrivere il codice di produzione che testano)
risposta data 29.03.2013 - 18:39
fonte

Leggi altre domande sui tag