it-swarm.it

Come scrivere "buoni" test unitari?

Innescato da questo thread , sto (di nuovo) sto pensando di utilizzare finalmente i test unitari nei miei progetti. Alcuni poster dicono qualcosa del tipo "I test sono belli, 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 risulta in una funzione di adattamento che può essere utilizzata per modellare questi dati. Ho trovato particolarmente difficile costruire test per questi metodi, poiché il numero di possibili input e risultati è troppo grande per testare ogni singolo caso, e i metodi stessi sono spesso piuttosto lunghi e non possono essere facilmente rifattorizzati senza sacrificare le prestazioni. Sono particolarmente interessato a test "buoni" per questo tipo di metodo.

63
Jens

The Art of Unit Testing ha il seguente da dire sui test unitari:

Un test unitario dovrebbe avere le seguenti proprietà:

  • Dovrebbe essere automatizzato e ripetibile.
  • Dovrebbe essere facile da implementare.
  • Una volta scritto, dovrebbe rimanere per uso futuro.
  • Chiunque dovrebbe essere in grado di eseguirlo.
  • Dovrebbe funzionare con la semplice pressione di un pulsante.
  • Dovrebbe funzionare rapidamente.

e poi aggiunge che dovrebbe essere completamente automatizzato, affidabile, leggibile e mantenibile.

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

A mio avviso, tutti questi sono molto importanti, ma gli ultimi tre (affidabili, leggibili e gestibili) in particolare, come se i tuoi test abbiano queste tre proprietà, di solito anche il tuo codice ne ha.

52
Andy Lowry

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 verificherebbe se un risultato è in realtà una media. Questo non ha alcun senso: stai rispecchiando (replicando) la funzionalità che stai testando. Se hai commesso un errore nella funzione principale, commetterai lo stesso errore nel test.

In altre parole, se ti ritrovi a replicare la funzionalità principale nel test unitario, è probabile che stai sprecando il tuo tempo.

43
mojuba

Buoni test unitari sono essenzialmente le specifiche in forma eseguibile:

  1. descrivere il comportamento del codice corrispondente ai casi d'uso
  2. coprire casi tecnici d'angolo (cosa succede se viene superato null) - se un test non è presente per un caso d'angolo, il comportamento non è definito.
  3. si interrompe se il codice testato cambia rispetto alle specifiche

Ho riscontrato che Test-Driven-Development è molto adatto per le routine di libreria poiché essenzialmente si scrive prima l'API e quindi l'implementazione effettiva.

10
user1249

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

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

Esempio di storia:

Come [pilota dell'X-Wing] voglio [non più dello 0,0001% errore di adattamento] in modo che [il computer di destinazione possa colpire la porta di scarico della Morte Nera quando si muove a tutta velocità attraverso un canyon box]

Quindi vai a parlare con i piloti (e con il computer di destinazione, se senziente). Prima parli di ciò che è "normale", poi parli dell'anormale. Scopri cosa conta davvero 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à, inclinazione, rollio, imbardata, vettore target, dimensione target e velocità target, e che questi valori saranno costanti o cambieranno linearmente. Anormalmente potresti avere meno canali e/o i valori potrebbero cambiare rapidamente. Quindi insieme ti vengono in mente 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 situazione particolare descritta nella storia. Si scopre, dopo aver parlato con il cliente e le altre parti interessate, che l'obiettivo nella storia originale era solo un esempio ipotetico. I veri test sono usciti dalla discussione che ne è seguita. Questo può succedere. La storia dovrebbe essere riscritta, ma non deve essere [poiché la storia è solo un segnaposto per una conversazione con il cliente].

7
Steven A. Lowe

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

5
user281377

Ho visto molti casi in cui le persone investono moltissimo sforzo nel scrivere test per il codice che viene inserito raramente e non nel scrivere test per il codice inserito frequentemente.

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

Inoltre, non credo nello scrivere test solo per il gusto di dire "Sì, lo testiamo". Se sto usando una libreria che viene rilasciata e rimarrà immutabile, non sprecherò un giorno a scrivere test per assicurarmi che le interiora di un'API che non cambieranno mai funzionino come previsto, anche se alcune parti di esso segnano in alto su un grafico di chiamata. Test che consumano detta libreria (il mio codice) lo indicano.

5
Tim Post

Non proprio TDD, ma dopo essere entrato nel QA puoi migliorare i tuoi test impostando casi di test per riprodurre eventuali bug che sorgono durante il processo di QA. Questo può essere particolarmente utile quando stai per ricevere supporto a lungo termine e inizi a raggiungere un luogo in cui rischi che le persone reintroducano inavvertitamente vecchi bug. Avere un test in atto per catturare è particolarmente prezioso.

4
glenatron

Provo ad avere ogni test solo una cosa. Provo a dare ad ogni test un nome come shouldDoSomething (). Provo a testare il comportamento, non l'implementazione. Metto alla prova solo metodi pubblici.

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

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

Se la classe A utilizza un'altra classe B, aggiungerei un'interfaccia, X, in modo che A non utilizzi direttamente B. Quindi creerei XMockup e lo userei al posto di B nei miei test. Aiuta davvero ad accelerare l'esecuzione del test, riducendo la complessità del test e riduce anche il numero di test che scrivo per A poiché non devo affrontare le peculiarità di B. Posso ad esempio testare che A chiama X.someMethod () invece di un effetto collaterale della chiamata a B.someMethod ().

Mantieni pulito anche il tuo codice di prova.

Quando si utilizza un'API, come un livello di database, lo deriderei e consentirei al mock-up di generare un'eccezione ad ogni possibile opportunità a comando. Quindi eseguo i test uno senza lanciare, e in un ciclo, ogni volta lanciando un'eccezione alla prossima opportunità fino a quando il test non si ripete. Un po 'come i test di memoria disponibili per Symbian.

3

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 offre Clean Code (132-133). Usa l'acronimo PRIMO (qui con i miei riassunti):

  • Veloce (dovrebbero funzionare rapidamente, quindi alla gente non dispiacerà eseguirli)
  • Indipendente (i test non devono essere configurati o smontati l'uno per l'altro)
  • Ripetibile (dovrebbe funzionare su tutti gli ambienti/piattaforme)
  • Auto-validante (completamente automatizzato; l'output dovrebbe essere "pass" o "fail", non un file di registro)
  • Puntuale (quando scriverli — poco prima di scrivere il codice di produzione che testano)
2
Kazark