Come si scrivono i casi di test unitario?

11

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

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

    
posta Vinoth Kumar C M 25.02.2011 - 19:27
fonte

6 risposte

12

Prova a leggere questo post del blog: Writing Great Test delle unità: le migliori e le peggiori pratiche .

Ma ci sono innumerevoli altri sul web.

In risposta diretta alle tue domande ...

  1. "Tendenza normale" - Immagino che questo potrebbe differire da un posto all'altro, quello che è normale per me potrebbe essere strano per gli altri.
  2. Direi (a mia discrezione) lo sviluppatore che scrive il codice dovrebbe scrivere il test, idealmente usando metodi come TDD, in cui scriveresti il test prima del codice. Ma altri potrebbero avere metodi e idee diversi qui!

E il modo in cui descrivi di scrivere i test (nella tua domanda) è completamente sbagliato !!

    
risposta data 25.02.2011 - 19:32
fonte
9

Questo approccio rende l'unità test inutile.

È necessario che il test dell'unità fallisca quando alcune azioni reali non funzionano come previsto. Se non lo fai così, e magari scrivi anche il test prima del codice da testare, è come avere un rilevatore di fumo non funzionante.

    
risposta data 25.02.2011 - 19:31
fonte
3

Se non sai cosa fa una funzione allora non puoi scrivere un unit test per questo. Per quel che ne sai, non fa nemmeno quello che dovrebbe. Devi scoprire cosa deve fare prima. POI scrivere il test.

    
risposta data 25.02.2011 - 20:32
fonte
2

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

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

Supponiamo che tu abbia 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 qualcuno l'ha fatto, vuoi affermare alcune proprietà di squareRoot:

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

Quindi inizi a scriverle come test individuali:

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

Oops, 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, introduci 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 oops, di nuovo:

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

Avresti dovuto provare:

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

Che cosa abbiamo fatto qui? Abbiamo iniziato con alcune ipotesi su come il metodo dovrebbe comportarsi e abbiamo scoperto che non tutte erano vere. Abbiamo quindi creato la suite di test Green, per dimostrare che il metodo si comporta in base alle nostre ipotesi corrette. Ora i client di questo codice possono fare affidamento su questo comportamento. Se qualcuno dovesse scambiare l'effettiva implementazione di squareRoot con qualcos'altro, qualcosa che per esempio ha davvero lanciato un'eccezione invece di restituire NaN, i nostri test l'avrebbero catturato immediatamente.

Questo esempio è banale, ma spesso si ereditano grandi pezzi di codice in cui non è chiaro cosa faccia effettivamente. In tal caso, è normale posizionare un cablaggio di test attorno al codice. Inizia con alcune ipotesi di base su come dovrebbe comportarsi il codice, scrivi test unitari per loro, prova. Se verde, bene, scrivi altri 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, riscrivi il test in modo che documenti il comportamento imprevisto:

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

Nel corso del tempo, si finisce con un'imbracatura di test che documenta come si comporta effettivamente il codice e diventa una sorta di specifica codificata. Se si desidera modificare il codice legacy o sostituirlo con qualcos'altro, si dispone del cablaggio di prova per verificare che il nuovo codice si comporti allo stesso modo o che il nuovo codice si comporti diversamente in modi previsti e controllati (ad esempio che in realtà corregge il bug che ci si aspetta che risolva). 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 poter scrivere il codice cliente con maggiore facilità, sapere dove aspettarsi che le cose si rompano quando si cambia qualcosa e dove si sono spezzate quando alla fine l'hanno fatto.

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

    
risposta data 11.10.2016 - 18:57
fonte
1

Quando scrivo casi di test (per stampanti) cerco di pensare a ogni piccolo componente ... e cosa posso fare per romperlo. Quindi, diciamo lo scanner per esempio, quali comandi usa (nel linguaggio pjl printer-job-language) cosa posso scrivere per testare ogni bit di funzionalità .... Ok ora cosa posso fare per provare a spezzarlo.

Cerco di farlo per ogni componente principale, ma quando si tratta di software e non di hardware, si vuole esaminare ciascun metodo / funzione e controllare i limiti e così via.

    
risposta data 25.02.2011 - 19:53
fonte
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 vorreste sicuramente sapere che cosa l'oggetto o il metodo che state testando dovrebbe fare, quindi creare un test per questo.

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 stub in modo che possa stabilire che i tuoi test funzionino correttamente quando il codice fallisce.

    
risposta data 25.02.2011 - 19:56
fonte

Leggi altre domande sui tag