it-swarm.it

Come si scrivono casi di test unitari?

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?

14
Vinoth Kumar C M

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, idealmente usando 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 !!

12
user18041

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.

9
user1249

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 è 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, si introduce double epsilon=0.01 e 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 annotare 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'imbracatura 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.

3
wallenborn

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
Edward Strange

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 utilizza (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.

Cerco di 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
user6791

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. È inoltre possibile creare una copia degli oggetti in prova con gli stub in modo da stabilire che i test funzionino correttamente in caso di errore del codice.

1
vjones