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:
- che può restituire radici semplici come sqrt (4.0)
- che può trovare una radice reale come sqrt (2.0) con una precisione ragionevole
- che trova che sqrt (0.0) è 0.0
- 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.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 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.