Come gestire il test che passa dall'inizio in TDD

8

Sto provando a praticare TDD nel mio progetto personale e mi chiedo come affrontare la situazione quando, dopo aver aggiunto un nuovo test, si passa dall'inizio in base all'implementazione esistente?

Da un lato, il nuovo test può fornire una documentazione aggiuntiva sul design e una protezione contro la violazione accidentale delle ipotesi.

D'altro canto, se il test passa senza alcun cambiamento di codice, allora è "sospetto" che verifichi o meno realmente ciò che dovrebbe.

In sostanza cosa si può fare per confermare la correttezza del test che asserisce un comportamento già implementato?

    
posta AGrzes 09.09.2017 - 20:41
fonte

2 risposte

13

Passare un test dal momento in cui lo hai scritto potrebbe essere un problema con il tuo processo TDD, ma non significa che sia sbagliato da solo.

Il tuo test potrebbe passare per una coincidenza.

Diciamo che hai un metodo che restituisce il costo del prelievo di denaro usando un bancomat. Ora ti viene chiesto di aggiungere ad esso la nuova regola che se il proprietario della carta ha più di 60 anni, il costo è 0. Quindi lo testiamo, aspettandoci che fallisca:

assertTrue(ATM.withdrawalCost(clientOver60) == 0)

Potresti aspettarti che questo fallisca. Ma passa, dal momento che il cliente sembra essere un cliente VIP, che ha prelievi gratuiti. Ora puoi tornare al metodo withdrawCost e modificarlo per farlo fallire, ma questo non ha molto senso. Scrivi un nuovo test per mostrare che il tuo codice è sbagliato:

assertTrue(ATM.withdrawalCost(nonVIPclientOver60) == 0)

Ora fallisce, vai a codice finché non passa, quindi ripeti fino a fine.

Dovresti cancellare il test, dato che non fa differenza? No! Descrive la funzionalità prevista del metodo di ritiro di ATM ATM. Se lo cancelli e un giorno il costo di ritiro dei costi 0 per i clienti VIP cambia, la prima affermazione dovrebbe essere ancora vera.

Detto questo, per il TDD corretto non dovresti codificare le cose prima dei tuoi test e poi provare le cose che sai che passerai. Non considero questo il caso che stai chiedendo.

Credo che il ciclo fail-code-pass sia pensato per evitare che "scriverò questo 3 test che falliranno e questo 2 che passerà perché ho già codificato sapendo quale sarebbe stato il test" lo sviluppo guidato . Dovresti sapere che alcune persone potrebbero sentirsi diversamente. Ascolta le loro ragioni, potrebbero anche essere valide.

    
risposta data 10.09.2017 - 03:03
fonte
10

I test che passano direttamente dall'inizio si verificano in genere quando si implementa qualcosa in un modo più generale di quello effettivamente necessario per i test in corso. Questo è abbastanza normale : i test di unità possono fornire solo un numero limitato e limitato di valori di input per una determinata funzione, ma la maggior parte delle funzioni è scritta per un ampio intervallo di valori di input possibili. Spesso un'implementazione progettata specificamente per i test case attuali sarebbe più complicata di una soluzione più generale. In questo caso, sarebbe ingombrante e soggetto a errori progettare artificialmente il codice in modo da funzionare solo per i casi di test e fallire per tutto il resto.

Ad esempio, diciamo che è necessaria una funzione per restituire il minimo di alcuni valori da un determinato array. Hai realizzato un'implementazione, guidata da un test con un array contenente solo uno o due valori. Ma invece di implementarlo in modo contorto eseguendo il confronto su diversi elementi (forse solo i primi due elementi), si chiama una funzione di minimo dell'array dalla libreria standard del proprio ecosistema linguistico e quindi si rende l'implementazione un unico elemento . Quando decidi di aggiungere un test con un array di cinque elementi, il test probabilmente passerà dall'inizio.

Ma come fai a sapere che il test non è "verde" a causa di un bug nel test stesso? Un modo semplice e diretto per avvicinarsi a questo è apportare una modifica temporanea all'oggetto sotto test per fare fallire il test. Ad esempio, potresti aggiungere intenzionalmente una riga if (array.size()==5) return 123 alla tua funzione. Ora il tuo test a cinque elementi fallirà, quindi sai

  • il test viene eseguito
  • la chiamata Assert nel test viene eseguita
  • la chiamata Assert nel test convalida la cosa giusta

che dovrebbe darti una certa sicurezza nel test. Dopo aver visto il test fallire, annullare la modifica e il test dovrebbe passare di nuovo.

In alternativa, puoi modificare il risultato previsto di un test: diciamo che il tuo test di passaggio contiene un'asserzione come

 int result = Subject.UnderTest(...);
 Assert.AreEqual(1,result);

quindi puoi modificare il test e sostituire "1" con "2". Quando il test fallisce (come previsto), sai che funziona come dovrebbe, e puoi annullare la sostituzione e vedere se il test diventa ora verde. Il rischio di introdurre un bug nel test con questo tipo di sostituzione è molto piccolo, quindi questo è probabilmente accettabile per la maggior parte dei casi reali.

Un modo diverso, forse discutibile, è di impostare un punto di interruzione nel test e utilizzare un debugger per eseguirlo. Ciò dovrebbe anche darti una certa sicurezza che il codice di prova sia effettivamente eseguito, e ti dà la possibilità di convalidare il percorso attraverso il test attraverso un'ispezione passo-passo. Tuttavia, si deve fare molta attenzione a non trascurare gli errori in un percorso di codice in modo specifico per un test in errore. Per test complessi, puoi prendere in considerazione l'idea di fare entrambe le cose - rendendo artificialmente e un debugger per controllarlo.

    
risposta data 10.09.2017 - 00:40
fonte

Leggi altre domande sui tag