Cosa dovresti testare con unit test?


122

Sono appena uscito dal college e inizierò l'università da qualche parte la prossima settimana. Abbiamo visto unit test, ma non li abbiamo usati molto; e tutti ne parlano, quindi ho pensato che forse avrei dovuto fare un po '.

Il problema è che non so cosa testare. Devo testare il caso comune? Il caso limite? Come faccio a sapere se una funzione è adeguatamente coperta?

Ho sempre la terribile sensazione che mentre un test dimostrerà che una funzione funziona per un determinato caso, è assolutamente inutile provare che la funzione funziona, punto.


Dai un'occhiata al blog di Roy Osherove . Ci sono molte informazioni sui test unitari ivi compresi i video. Ha anche scritto un libro, "L'arte del test unitario" che è molto buono.
Piers Myers,

9
Mi chiedo cosa ne pensi dopo quasi 5 anni? Perché sempre più sento che le persone dovrebbero sapere meglio "cosa non testare l'unità" al giorno d'oggi. Lo sviluppo basato sul comportamento si è evoluto dalle domande che hai posto.
Remigijus Pankevičius,

Risposte:


121

La mia filosofia personale è stata così:

  1. Metti alla prova il caso comune di tutto ciò che puoi. Questo ti dirà quando quel codice si romperà dopo aver apportato alcune modifiche (che è, a mio avviso, il principale vantaggio dei test di unità automatizzati).
  2. Testa i casi limite di alcuni codici insolitamente complessi che pensi possano avere errori.
  3. Ogni volta che trovi un bug, scrivi un test case per coprirlo prima di risolverlo
  4. Aggiungi i test del caso limite al codice meno critico ogni volta che qualcuno ha il tempo di uccidere.

1
Grazie per questo, mi sono imbattuto qui con le stesse domande dell'OP.
Stephen,

5
+1, anche se testerei anche i casi limite di qualsiasi funzione di libreria / utilità per assicurarmi di avere un'API logica. ad es. cosa succede quando viene passato un null? che dire di input vuoto? Ciò contribuirà a garantire che il tuo design sia logico e documenterà il comportamento del case d'angolo.
Mikera,

7
# 3 sembra una risposta molto solida in quanto è un esempio di vita reale di come un test unitario avrebbe potuto aiutare. Se si è rotto una volta, può rompersi di nuovo.
Ryan Griffith,

Avendo appena iniziato, trovo che non sono molto creativo con l'ideazione di test. Quindi li uso come sopra n. 3, il che garantisce la tranquillità che quei bug non saranno mai più non rilevati.
ankush981

La tua risposta è stata pubblicata in questo popolare articolo di media: hackernoon.com/…
BugHunterUK il

67

Tra la pletora di risposte finora nessuno ha toccato il partizionamento dell'equivalenza e l' analisi del valore limite , considerazioni vitali nella risposta alla domanda in questione. Tutte le altre risposte, sebbene utili, sono qualitative ma è possibile - e preferibile - essere quantitative qui. @fishtoaster fornisce alcune linee guida concrete, che danno una sbirciatina sotto le copertine della quantificazione dei test, ma il partizionamento dell'equivalenza e l'analisi del valore limite ci consentono di fare meglio.

Nel partizionamento di equivalenza , dividi l'insieme di tutti i possibili input in gruppi in base ai risultati previsti. Qualsiasi input da un gruppo produrrà risultati equivalenti, quindi tali gruppi sono chiamati classi di equivalenza . (Notare che risultati equivalenti non significano risultati identici.)

A titolo di esempio, si consideri un programma che dovrebbe trasformare caratteri ASCII minuscoli in caratteri maiuscoli. Gli altri personaggi dovrebbero subire una trasformazione dell'identità, cioè rimanere invariati. Ecco una possibile suddivisione in classi di equivalenza:

| # |  Equivalence class    | Input        | Output       | # test cases |
+------------------------------------------------------------------------+
| 1 | Lowercase letter      | a - z        | A - Z        | 26           |
| 2 | Uppercase letter      | A - Z        | A - Z        | 26           |
| 3 | Non-alphabetic chars  | 0-9!@#,/"... | 0-9!@#,/"... | 42           |
| 4 | Non-printable chars   | ^C,^S,TAB... | ^C,^S,TAB... | 34           |

L'ultima colonna riporta il numero di casi di test se li si elencano tutti. Tecnicamente, secondo la regola 1 di @ fishtoaster includeresti 52 casi di test - tutti quelli per le prime due file sopra riportati rientrano nel "caso comune". La regola 2 di @ fishtoaster aggiungerebbe anche alcune o tutte le righe 3 e 4 sopra. Ma con il test del partizionamento di equivalenza è sufficiente un solo caso di test in ciascuna classe di equivalenza. Se scegli "a" o "g" o "w" stai testando lo stesso percorso di codice. Pertanto, hai un totale di 4 casi di test anziché 52+.

L'analisi del valore limite suggerisce un leggero affinamento: in sostanza suggerisce che non tutti i membri di una classe di equivalenza sono, beh, equivalenti. Cioè, i valori ai confini dovrebbero essere considerati degni di un caso di prova a sé stanti. (Una semplice giustificazione per questo è il famigerato errore off-by-one !) Pertanto, per ogni classe di equivalenza potresti avere 3 input di test. Osservando il dominio di input sopra - e con una certa conoscenza dei valori ASCII - potrei trovare questi input di test case:

| # | Input                | # test cases |
| 1 | a, w, z              | 3            |
| 2 | A, E, Z              | 3            |
| 3 | 0, 5, 9, !, @, *, ~  | 7            |
| 4 | nul, esc, space, del | 4            |

(Non appena ottieni più di 3 valori limite che suggeriscono che potresti voler ripensare le delineazioni originali della classe di equivalenza, ma questo è stato abbastanza semplice da non tornare indietro per rivederle.) Pertanto, l'analisi del valore limite ci porta solo a 17 casi di test - con un'alta affidabilità di copertura completa - rispetto a 128 casi di test per eseguire test esaustivi. (Per non parlare del fatto che la combinatoria impone che test esaustivi siano semplicemente impossibili per qualsiasi applicazione nel mondo reale!)


3
+1 È esattamente così che scrivo intuitivamente i miei test. Ora posso farci un nome :) Grazie per averlo condiviso.
guillaume31,

+1 per "le risposte qualitative sono utili, ma è possibile - e preferibile - essere quantitativo"
Jimmy Breck-McKye il

Penso che questa sia una buona risposta se la direttiva è "come posso ottenere una buona copertura con i miei test". Penso che sarebbe utile trovare un approccio pragmatico oltre a questo - l'obiettivo è che ogni branca di ogni pezzo di logica in ogni strato dovrebbe essere testato accuratamente in questo modo?
Kieren Johnstone,

18

Probabilmente la mia opinione non è troppo popolare. Ma ti suggerisco di essere economico con i test unitari. Se hai troppi test unitari puoi facilmente passare la metà del tuo tempo o più a mantenere i test piuttosto che la codifica effettiva.

Ti suggerisco di scrivere dei test per cose che hai un brutto istinto o cose molto cruciali e / o elementari. I test unitari IMHO non sostituiscono una buona ingegneria e codifica difensiva. Attualmente lavoro su un progetto che è più o meno inusuale. È davvero stabile ma è un dolore da refactoring. In effetti nessuno ha toccato questo codice in un anno e lo stack software su cui si basa ha 4 anni. Perché? Perché è ingombro di test unitari, per essere precisi: test unitari e test di integrazione automatizzati. (Hai mai sentito parlare di cetrioli e simili?) Ed ecco la parte migliore: questo (ancora) inutile pezzo di software è stato sviluppato da un'azienda i cui dipendenti sono pionieri nella scena dello sviluppo guidato dai test. : D

Quindi il mio suggerimento è:

  • Inizia a scrivere test dopo aver sviluppato lo scheletro di base, altrimenti il ​​refactoring può essere doloroso. Come sviluppatore che sviluppa per gli altri non ottieni mai i requisiti all'inizio.

  • Assicurarsi che i test unitari possano essere eseguiti rapidamente. Se hai dei test di integrazione (come il cetriolo), va bene se impiegano un po 'più di tempo. Ma i test di lunga durata non sono divertenti, credimi. (Le persone dimenticano tutti i motivi per cui il C ++ è diventato meno popolare ...)

  • Lascia questa roba TDD agli esperti TDD.

  • E sì, a volte ti concentri sui casi limite, a volte sui casi comuni, a seconda di dove ti aspetti l'imprevisto. Tuttavia, se ti aspetti sempre l'imprevisto, dovresti davvero ripensare il flusso di lavoro e la disciplina. ;-)


2
Puoi fornire maggiori dettagli sul motivo per cui i test rendono questo software un problema per il refactoring?
Mike Partridge,

6
Grande +1. Avere muri di unit test che testano l'implementazione invece delle regole rende ogni cambiamento richiede 2-3 volte il numero
TheLQ

9
Come il codice di produzione scritto male, i test unitari scritti male sono difficili da mantenere. "Troppi test unitari" sembra un fallimento nel rimanere ASCIUTTO; ogni test dovrebbe affrontare / dimostrare una parte specifica del sistema.
Allan,

1
Ogni test unitario dovrebbe controllare una cosa, quindi non ci sono troppi test unitari, ma test mancanti. Se i test unitari sono complessi, questo è un altro problema.
graffic

1
-1: penso che questo post sia scritto male. Ci sono più cose menzionate e non so come siano tutte correlate. Se il punto della risposta è "essere economico", come si collega il tuo esempio? Sembra che la tua situazione di esempio (sebbene reale) abbia dei test unitari errati. Per favore, spiega quali lezioni dovrei imparare da ciò e come mi aiuta ad essere economico. Inoltre, onestamente, semplicemente non so cosa intendi quando dici Leave this TDD stuff to the TDD-experts.
Alexander Bird,

8

Se stai testando prima con Test Driven Development, la tua copertura sarà nel range del 90% o superiore, perché non aggiungerai funzionalità senza prima scrivere un test unitario fallito.

Se stai aggiungendo test dopo il fatto, allora non posso raccomandare abbastanza di ottenere una copia di Working Effectively With Legacy Code di Michael Feathers e dare un'occhiata ad alcune delle tecniche sia per aggiungere test al tuo codice sia per modi di refactoring del tuo codice per renderlo più testabile.


Come si calcola quella percentuale di copertura? Cosa significa comunque coprire il 90% del tuo codice?
zneak,

2
@zneak: ci sono strumenti di copertura del codice che li calcoleranno per te. Un rapido google per la "copertura del codice" dovrebbe farne apparire alcuni. Lo strumento tiene traccia delle righe di codice che vengono eseguite durante l'esecuzione dei test e basa le linee di codice totali nelle assiemi di assemblaggio per ottenere la percentuale di copertura.
Steven Evers,

-1. Non risponde alla domanda:The problem is, I don't know _what_ to test
Alexander Bird,

6

Se inizi a seguire le pratiche di sviluppo guidato dai test , questi ti guideranno attraverso il processo e sapranno cosa testare arriverà naturalmente. Alcuni punti da cui iniziare:

I test vengono prima di tutto

Mai e poi mai scrivere il codice prima di scrivere i test. Vedi Red-Green-Refactor-Repeat per una spiegazione.

Scrivi test di regressione

Ogni volta che incontri un bug, scrivi una testcase e assicurati che fallisca . A meno che tu non riesca a riprodurre un bug attraverso un testcase fallito, non lo hai davvero trovato.

Rosso-Verde-Refactor-repeat

Rosso : iniziare scrivendo un test di base per il comportamento che si sta tentando di implementare. Pensa a questo passaggio come a scrivere un codice di esempio che utilizza la classe o la funzione su cui stai lavorando. Assicurarsi che non compili / non presenti errori di sintassi e che non abbia esito positivo . Questo dovrebbe essere ovvio: non hai scritto alcun codice, quindi deve fallire, giusto? La cosa importante da imparare qui è che se non vedi il test fallire almeno una volta, non puoi mai essere sicuro che se lo supera, lo fa a causa di qualcosa che hai fatto a causa di una ragione fasulla.

Verde : scrivi il codice più semplice e stupido che in realtà fa passare il test. Non cercare di essere intelligente. Anche se vedi che c'è un caso limite evidente ma il test prende in considerazione, non scrivere codice per gestirlo (ma non dimenticare il caso limite: ne avrai bisogno in seguito). L'idea è che ogni pezzo di codice che scrivi, ogni if, ogni try: ... except: ...dovrebbe essere giustificato da un test case. Il codice non deve essere elegante, veloce o ottimizzato. Vuoi solo che il test passi.

Refactor : ripulisci il tuo codice, ottieni i nomi dei metodi giusti. Verifica se il test sta ancora superando. Ottimizzare. Eseguire di nuovo il test.

Ripeti : ricordi il caso limite che il test non ha coperto, giusto? Quindi, ora è il suo grande momento. Scrivi una testcase che copre quella situazione, guardala fallire, scrivi un po 'di codice, guardalo passare, refactor.

Metti alla prova il tuo codice

Stai lavorando su un pezzo di codice specifico, ed è esattamente quello che vuoi testare. Questo significa che non dovresti testare le funzioni della libreria, la libreria standard o il tuo compilatore. Inoltre, cerca di evitare di testare il "mondo". Ciò include: chiamare API Web esterne, alcuni elementi ad uso intensivo del database, ecc. Ogni volta che puoi provare a simularlo (crea un oggetto che segue la stessa interfaccia, ma restituisce dati statici predefiniti).


1
Supponendo che ho già una base di codice esistente e (per quanto posso vedere) funzionante, cosa devo fare?
zneak,

Questo potrebbe essere leggermente più difficile (a seconda di come viene scritto il codice). Inizia con i test di regressione (hanno sempre senso), quindi puoi provare a scrivere unit test per dimostrare a te stesso che hai capito cosa sta facendo il codice. È facile essere sopraffatti dalla quantità di lavoro che (apparentemente) deve fare, ma: alcuni test sono sempre meglio di nessun test.
Ryszard Szopa,

3
-1 Non credo sia un'ottima risposta a questa domanda . La domanda non riguarda TDD, si chiede cosa testare quando si scrivono unit test. Penso che una buona risposta alla domanda reale dovrebbe applicarsi a una metodologia non TDD.
Bryan Oakley,

1
Se lo tocchi, provalo. E Clean Code (Robert C Martin) suggerisce di scrivere "test di apprendimento" per codice di terze parti. In questo modo impari a usarlo e hai dei test nel caso in cui una nuova versione cambi il comportamento che stai usando.
Roger Willcocks,

3

Per i test unitari, inizia con i test che fanno ciò per cui è stato progettato. Questo dovrebbe essere il primo caso che scrivi. Se parte del disegno è "dovrebbe generare un'eccezione se passi nella spazzatura", prova anche questo dato che fa parte del disegno.

Inizia con quello. Man mano che acquisisci esperienza con la maggior parte dei test di base, inizierai a capire se è sufficiente o meno e inizierai a vedere altri aspetti del tuo codice che necessitano di test.


0

La risposta di borsa è "testare tutto ciò che potrebbe rompersi" .

Cosa c'è di troppo semplice per rompere? Campi di dati, accessori di proprietà cerebrali e simili overhead della caldaia. Qualcos'altro probabilmente implementa una parte identificabile di un requisito e può trarre vantaggio dal test.

Naturalmente, il tuo chilometraggio - e le pratiche del tuo ambiente di lavoro - possono variare.


Va bene. Quindi quali casi devo testare? Il caso "normale"? Il caso limite?
zneak,

3
Regola del pollice? Uno o due proprio nel mezzo del sentiero d'oro, e appena dentro e appena fuori da ogni bordo.
Jeffrey Hantin,

@JeffreyHantin Questa è l '"analisi del valore limite" in una risposta diversa.
Roger Willcocks,
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.