Come si scrivono casi di test unitari?


14

A volte finisco per scrivere casi di test unitari per il codice che altri sviluppatori hanno scritto. Ci sono occasioni in cui davvero non so cosa stia cercando di fare lo sviluppatore (la parte aziendale) e manipolo il test case per ottenere la linea verde. Queste cose sono normali nel settore?

Qual è la tendenza normale? Gli sviluppatori dovrebbero scrivere casi di unit test per il codice che hanno scritto loro stessi?


2
"forza"? Che cosa significa "dint"?
S.Lott

Risposte:


12

Prova a leggere questo post sul blog: scrivere grandi test unitari: migliori e peggiori pratiche .

Ma ce ne sono innumerevoli altri sul web.

In risposta diretta alle tue domande ...

  1. "Tendenza normale" - Immagino che questo potrebbe differire da un posto all'altro, ciò che per me normale potrebbe essere strano per gli altri.
  2. Direi (nella mia opzione) lo sviluppatore che scrive il codice dovrebbe scrivere il test, usando idealmente metodi come TDD, dove scrivere il test prima del codice. Ma altri possono avere metodi e idee diversi qui!

E il modo in cui hai descritto di scrivere i test (nella tua domanda) è totalmente sbagliato !!


9

Questo approccio rende inutile il test unitario.

È necessario che il test unitario fallisca quando qualche azione reale non funziona come previsto. Se non lo fai in questo modo, e forse addirittura scrivi il test prima del codice da testare, è come avere degli allarmi antincendio non funzionanti.


8
Questo non è del tutto vero. O meglio, è vero in un mondo ideale, ma purtroppo spesso siamo tutt'altro. Considera di avere un codice legacy senza test e senza specifiche, e senza nessuno che possa dirti in modo affidabile fino ai minimi dettagli, cosa dovrebbe fare esattamente un determinato pezzo di codice (questa è la realtà in gran parte dei progetti esistenti). Anche in questo caso, può valere la pena scrivere unit test per bloccare lo stato corrente del codice e per assicurarsi di non interrompere nulla con futuri refactoring, correzioni di bug o estensioni.
Péter Török,

2
Inoltre, suppongo che intendevi "scrivere il test dopo il codice da testare", vero?
Péter Török,

@Péter, il testo è andato storto - hai capito bene. Ma, se decidi di scrivere dei test, dovrebbero fare qualcosa per essere utili. Il fatto di invocare ciecamente il codice dicendo che si tratta di un test, secondo me, non sta testando.

In ogni caso, se intendi dire che dobbiamo avere affermazioni significative nei nostri test unitari, per verificare che il codice testato faccia effettivamente quello che pensiamo faccia, sono pienamente d'accordo.
Péter Török,

3

Se non sai cosa fa una funzione, non puoi scrivere un test unitario per essa. Per quello che sai, non fa nemmeno quello che dovrebbe. Devi scoprire prima cosa dovrebbe fare. Quindi scrivere il test.


3

Nel mondo reale, è perfettamente normale scrivere unit test per il codice di qualcun altro. Certo, lo sviluppatore originale avrebbe dovuto farlo già, ma spesso ricevi codice legacy dove questo non è stato fatto. A proposito, non importa se quel codice legacy è arrivato decenni fa da una galassia molto, molto lontana, o se uno dei tuoi colleghi lo ha controllato la scorsa settimana, o se lo hai scritto oggi, il codice legacy è un codice senza test

Chiediti: perché scriviamo unit test? Going Green è ovviamente solo un mezzo per raggiungere un fine, l'obiettivo finale è dimostrare o confutare le affermazioni sul codice in prova.

Supponi di avere un metodo che calcola la radice quadrata di un numero in virgola mobile. In Java, l'interfaccia lo definirebbe come:

public double squareRoot(double number);

Non importa se hai scritto l'implementazione o se qualcun altro l'ha fatto, vuoi affermare alcune proprietà di squareRoot:

  1. che può restituire radici semplici come sqrt (4.0)
  2. che può trovare una radice reale come sqrt (2.0) con una precisione ragionevole
  3. che trova che sqrt (0.0) è 0.0
  4. che genera un'eccezione IllegalArgumentException quando viene immesso un numero negativo, ovvero su sqrt (-1.0)

Quindi inizi a scrivere questi come test individuali:

@Test
public void canFindSimpleRoot() {
  assertEquals(2, squareRoot(4), epsilon);
}

Spiacenti, questo test ha già esito negativo:

java.lang.AssertionError: Use assertEquals(expected, actual, delta) to compare floating-point numbers

Hai dimenticato l'aritmetica in virgola mobile. OK, ti presenti double epsilon=0.01e vai:

@Test
public void canFindSimpleRootToEpsilonPrecision() {
  assertEquals(2, squareRoot(4), epsilon);
}

e aggiungi gli altri test: finalmente

@Test
@ExpectedException(IllegalArgumentException.class)
public void throwsExceptionOnNegativeInput() {
  assertEquals(-1, squareRoot(-1), epsilon);
}

e ops, ancora:

java.lang.AssertionError: expected:<-1.0> but was:<NaN>

Avresti dovuto testare:

@Test
public void returnsNaNOnNegativeInput() {
  assertEquals(Double.NaN, squareRoot(-1), epsilon);
}

Cosa abbiamo fatto qui? Abbiamo iniziato con alcune ipotesi su come dovrebbe comportarsi il metodo e abbiamo scoperto che non tutti erano veri. Abbiamo quindi creato la suite di test Green, per scrivere la prova che il metodo si comporta secondo i nostri presupposti corretti. Ora i client di questo codice possono fare affidamento su questo comportamento. Se qualcuno dovesse scambiare l'implementazione effettiva di squareRoot con qualcos'altro, qualcosa che, ad esempio, ha lanciato un'eccezione invece di restituire NaN, i nostri test lo catturerebbero immediatamente.

Questo esempio è banale, ma spesso erediti grandi parti di codice in cui non è chiaro ciò che effettivamente fa. In tal caso, è normale posizionare un cablaggio di prova attorno al codice. Inizia con alcune ipotesi di base su come dovrebbe comportarsi il codice, scrivi unit test per loro, test. Se verde, bene, scrivi più test. Se rosso, beh, ora hai un'affermazione fallita che puoi tenere contro una specifica. Forse c'è un bug nel codice legacy. Forse le specifiche non sono chiare su questo particolare input. Forse non hai una specifica. In tal caso, riscrivere il test in modo tale da documentare il comportamento imprevisto:

@Test
public void throwsNoExceptionOnNegativeInput() {
  assertNotNull(squareRoot(-1)); // Shouldn't this fail?
}

Nel tempo, si finisce con un cablaggio di prova che documenta il comportamento effettivo del codice e diventa una specie di specifica codificata. Se vuoi mai cambiare il codice legacy o sostituirlo con qualcos'altro, hai il cablaggio di prova per verificare che il nuovo codice si comporti allo stesso modo o che il nuovo codice si comporti in modo diverso nei modi previsti e controllati (ad esempio che effettivamente corregge il bug che prevedi venga risolto). Questa imbracatura non deve essere completa il primo giorno, infatti, avere un'imbragatura incompleta è quasi sempre meglio che non avere alcuna imbracatura. Avere un'imbracatura significa che puoi scrivere il tuo codice cliente con più facilità, sai dove aspettarti che le cose si rompano quando cambi qualcosa e dove si sono rotti quando alla fine lo hanno fatto.

Dovresti cercare di uscire dalla mentalità che devi scrivere unit test solo perché devi, come se dovessi compilare campi obbligatori in un modulo. E non dovresti scrivere unit test solo per rendere verde la linea rossa. I test unitari non sono i tuoi nemici, i test unitari sono i tuoi amici.


1

Quando scrivo casi di test (per stampanti) provo a pensare a ogni piccolo componente .... e cosa posso fare per romperlo. Quindi diciamo allo scanner, ad esempio, quali comandi usa (nel linguaggio di lavoro della stampante pjl) cosa posso scrivere per testare ogni bit di funzionalità .... Ok ora cosa posso fare per provare a romperlo.

Provo a farlo per ogni componente principale, ma quando si tratta di software e non tanto hardware si desidera esaminare ogni metodo / funzione e controllare i limiti e così via.


1

Sembra che tu stia lavorando con altri sviluppatori (o mantenendo il codice scritto da altri sviluppatori) che non eseguono test unitari. In tal caso, penso che vorresti sicuramente sapere quale dovrebbe essere l'oggetto o il metodo che stai testando, quindi crearne uno.

Non sarà TDD perché non hai scritto prima il test, ma potresti migliorare la situazione. Potresti anche voler creare una copia degli oggetti sotto test con gli stub in modo che possa stabilire che i tuoi test funzionino correttamente quando il codice fallisce.

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.