The thing I don't get the most is, how can a unit test tell me whether
my calculate price function (which can depend on things like the day
of the week and bank holidays etc, so assume 20-40 lines for that
function) is correct? Would it not be quicker for me to write all the
code and the sit through a debugging session to test every eventually
of the code?
Facciamo questo l'esempio. Se ti siedi e scrivi quello (chiamiamolo) metodo a 30 righe, proverai a pensare a tutte le possibilità, scriverle nel metodo, poi eseguirne il debug, cercando ancora di prendere in considerazione tutte le possibilità. Se sei andato a controllare per giorni della settimana e sei andato in vacanza, e hai trovato un bug, allora devi cambiare il metodo, e sarebbe facile cambiarlo in modo che ora funzioni correttamente per i giorni festivi, ma non per i fine settimana, ma dal momento che hai già controllato i fine settimana e ha funzionato, potresti dimenticarti di ripetere l'esame. Questo è solo uno scenario in cui gli errori si insinuano nel tuo prodotto con il tuo approccio.
Un altro problema è che può essere facile aggiungere codice per condizioni che non si verificano mai in effetti, a causa di un'eccessiva cautela. Qualsiasi codice non necessario aggiunge complessità al progetto e la complessità rende più difficile il debug e altre attività di manutenzione.
In che modo TDD ti protegge da questi problemi? Inizi a scrivere il caso più semplice e il codice più semplice che lo supererà. Supponiamo che il tuo prezzo predefinito sia $ 7. Scriverò questo Java-ish, perché è comodo, ma l'approccio funziona in qualsiasi lingua:
public void testDefaultPrice() throws Exception {
assertEquals(7, subject.getPrice());
}
public int getPrice() {
return 7;
}
Semplice come può essere, giusto? Incomplete, ma corrette fin dove siamo andati.
Ora diremo che il prezzo del weekend deve essere $ 9. Ma prima che possiamo arrivarci, dobbiamo sapere quali giorni costituiscono il fine settimana.
public void testWeekend() throws Exception {
assertTrue(Schedule.isWeekend(Weekday.Sunday));
}
public boolean isWeekend(Weekday.Day day) {
return true;
}
Fin qui tutto bene - e ancora molto incompleto.
public void testWeekend() throws Exception {
assertTrue(Schedule.isWeekend(Weekday.Sunday));
assertTrue(Schedule.isWeekend(Weekday.Saturday));
assertFalse(Schedule.isWeekend(Weekday.Monday));
}
public boolean isWeekend(Weekday.Day day) {
return day == Weekday.Sunday || day == Weekday.Saturday;
}
Aggiungi tutte le affermazioni di cui hai bisogno per essere sicuro di aver guidato il metodo alla correttezza.
Ora torna alla nostra classe originale:
public void testDefaultPrice() throws Exception {
assertEquals(7, subject.getPrice());
}
public void testWeekendPrice() throws Exception {
subject.setWeekday(Weekday.Sunday);
assertEquals(9, subject.getPrice());
}
public int getPrice() {
if (Schedule.isWeekend(day))
return 9;
return 7;
}
E così via. Si prega di notare, inoltre, come stiamo testando il design del nostro codice qui, e quanto è meglio per questo. Con il tuo approccio, la maggior parte dei programmatori avrebbe costruito il codice di test del fine settimana nel corpo di getPrice()
, ma quello è il posto sbagliato. Un sacco di codice potrebbe voler sapere se un giorno è nel fine settimana; in questo modo lo hai in un unico luogo ben testato. Ciò promuove il riutilizzo e migliora la manutenibilità.