Come correggere un errore nel test, dopo aver scritto l'implementazione

21

Qual è la migliore linea d'azione in TDD se, dopo aver implementato correttamente la logica, il test fallisce ancora (perché c'è un errore nel test)?

Ad esempio, supponiamo che ti piacerebbe sviluppare la seguente funzione:

int add(int a, int b) {
    return a + b;
}

Supponiamo di svilupparlo nei seguenti passaggi:

  1. Scrivi test (nessuna funzione ancora):

    // test1
    Assert.assertEquals(5, add(2, 3));
    

    Risultati in errore di compilazione.

  2. Scrivi un'implementazione di una funzione fittizia:

    int add(int a, int b) {
        return 5;
    }
    

    Risultato: test1 passaggi.

  3. Aggiungi un altro test case:

    // test2 -- notice the wrong expected value (should be 11)!
    Assert.assertEquals(12, add(5, 6));
    

    Risultato: test2 fallisce, test1 ancora passa.

  4. Scrivi un'implementazione reale:

    int add(int a, int b) {
        return a + b;
    }
    

    Risultato: test1 continua a passare, test2 non riesce ancora (poiché 11 != 12 ).

In questo caso particolare: sarebbe meglio:

  1. correggi test2 , e vedi che ora passa, o
  2. elimina la nuova porzione di implementazione (es. torna al punto 2 sopra), correggi test2 e lascia fallire, quindi reintroduce l'implementazione corretta (passaggio # 4 sopra).

O esiste un altro modo più intelligente?

Pur comprendendo che il problema di esempio è piuttosto banale, sono interessato a cosa fare nel caso generico, che potrebbe essere più complesso dell'aggiunta di due numeri.

EDIT (In risposta alla risposta di @Thomas Junk):

L'obiettivo di questa domanda è quello che TDD suggerisce in questo caso, non quello che è "la migliore pratica universale" per ottenere buoni codici o test (che potrebbero essere diversi dal modo TDD).

    
posta Attilio 28.01.2017 - 17:45
fonte

5 risposte

31

La cosa assolutamente critica è che vedi il test passare e fallire.

Se si cancella il codice per rendere fallito il test, quindi riscrivere il codice o inserirlo di nascosto negli Appunti solo per incollarlo in un secondo momento non importa. TDD non ha mai detto che dovevi ridigitare qualsiasi cosa. Vuole sapere che il test passa solo quando dovrebbe passare e fallisce solo quando dovrebbe fallire.

Vedere il test sia in positivo che in negativo è come testare il test. Non fidarti mai di un test che non hai mai visto fare entrambi.

Refactoring Against The Red Bar ci fornisce i passaggi formali per il refactoring di un test di lavoro:

  • Esegui il test
    • Prendi nota della barra verde
    • Rompere il codice sottoposto a test
  • Esegui il test
    • Prendi nota della barra rossa
    • Rifattore test
  • Esegui il test
    • Prendi nota della barra rossa
    • Annulla l'interruzione del codice sottoposto a test
  • Esegui il test
    • Prendi nota della barra verde

Tuttavia, non stiamo effettuando il refactoring di un test di lavoro. Dobbiamo trasformare un buggy test. Una preoccupazione è il codice che è stato introdotto mentre solo questo test lo riguardava. Tale codice dovrebbe essere ripristinato e reintrodotto una volta risolto il test.

Se ciò non è il caso, e la copertura del codice non è una preoccupazione a causa di altri test che coprono il codice, è possibile trasformare il test e introdurlo come test verde.

Anche qui viene eseguito il rollback del codice, ma quel tanto che basta per far fallire il test. Se questo non è sufficiente per coprire tutto il codice introdotto mentre è coperto solo dal test del buggy, abbiamo bisogno di un rollback di codice più grande e più test.

Introduci un test verde

  • Esegui il test
    • Prendi nota della barra verde
    • Rompere il codice sottoposto a test
  • Esegui il test
    • Prendi nota della barra rossa
    • Annulla l'interruzione del codice sottoposto a test
  • Esegui il test
    • Prendi nota della barra verde

Rompendo il codice puoi commentare il codice o spostarlo altrove solo per incollarlo in un secondo momento. Questo ci mostra lo scopo del codice delle copertine dei test.

Per queste ultime due corse sei di nuovo nel normale ciclo rosso-verde. Stai semplicemente incollando invece di digitare per annullare l'interruzione del codice e passare il test. Quindi assicurati di incollare quanto basta per far passare il test.

Lo schema generale qui è vedere il colore del test cambiare nel modo in cui ci aspettiamo. Nota che questo crea una situazione in cui hai brevemente un test verde non affidabile. Fai attenzione a essere interrotto e dimentica dove ti trovi in questi passaggi.

I miei ringraziamenti a RubberDuck per Abbracciare la barra rossa collegamento.

    
risposta data 29.01.2017 - 03:04
fonte
10

Qual è l'obiettivo generale che vuoi raggiungere?

  • Fare bei test?

  • Realizzare l'implementazione corretta ?

  • facendo TTD religiosamente giusto ?

  • Nessuna delle precedenti?

Forse pensi troppo alla tua relazione con i test e ai test.

I test non forniscono garanzie sulla correttezza di un'implementazione. Il passaggio di tutti i test non dice nulla, indipendentemente dal fatto che il software faccia ciò che dovrebbe; non rilascia dichiarazioni essenziali sul tuo software.

Prendendo il tuo esempio:

L'implementazione "corretta" dell'aggiunta sarebbe il codice equivalente a a+b . E fintanto che il tuo codice fa , tu diresti che l'algoritmo è corretto in quello che fa ed è correttamente implementato.

int add(int a, int b) {
    return a + b;
}

Sulla prima vista , entrambi dovremmo essere d'accordo, che questa è l'implementazione di un'aggiunta.

Ma ciò che stiamo facendo in realtà non sta dicendo che questo codice è l'implementazione di addition si comporta solo in una certa misura like one: pensa a overflow di interi .

Overflow intero si verifica nel codice, ma non nel concetto di addition . Quindi: il tuo codice si comporta in una certa misura come il concetto di addition , ma non è addition .

Questo punto di vista piuttosto filosofico ha diverse conseguenze.

E uno è, si potrebbe dire, i test non sono altro che ipotesi del comportamento previsto del codice. Nel testare il tuo codice, potresti (forse) non esserti mai assicurato che la tua implementazione è giusta , il meglio che potresti dire è che le tue aspettative su quali risultati il tuo codice ha prodotto sono state o non sono stati raggiunti; Sia che il tuo codice sia sbagliato, sia che il tuo test sia sbagliato o che sia sbagliato, che entrambi stiano sbagliando.

I test utili ti aiutano a correggere le tue aspettative su cosa dovrebbe fare il codice: finché non cambio le mie aspettative e finché il codice modificato mi dà il risultato che mi aspetto, io potrebbe essere sicuro, che le supposizioni che ho fatto sui risultati sembrano funzionare.

Questo non aiuta, quando hai fatto le ipotesi sbagliate; ma hey! almeno previene la schizofrenia: aspettandosi risultati diversi quando non ce ne dovrebbe essere nessuno.

tl; dr

What is the best course of action in TDD if, after implementing the logic correctly, the test still fails (because there is a mistake in the test)?

I tuoi test sono supposizioni sul comportamento del codice. Se hai buone ragioni per pensare che la tua implementazione sia corretta, correggi il test e verifica se tale ipotesi è valida.

    
risposta data 29.01.2017 - 13:20
fonte
8

Devi sapere che il test fallirà se l'implementazione è sbagliata, che non è la stessa cosa che passa se l'implementazione è corretta. Pertanto dovresti riportare il codice in uno stato in cui prevedi che fallisca prima correggendo il test, e assicurati che fallisca per il motivo che ti aspettavi (cioè 5 != 12 ), piuttosto che qualcos'altro tu non ha previsto.

    
risposta data 28.01.2017 - 18:44
fonte
4

In questo caso particolare, se cambi il 12 in un 11 e il test ora passa, penso che hai fatto un buon lavoro di test del test e dell'implementazione, quindi non c'è molto bisogno di passare attraverso cerchi aggiuntivi.

Tuttavia, lo stesso problema può verificarsi in situazioni più complesse, ad esempio quando si verifica un errore nel codice di configurazione. In tal caso, dopo aver corretto il test, dovresti probabilmente provare a modificare la tua implementazione in modo da non riuscire a eseguire quel particolare test e quindi ripristinare la mutazione. Se ripristinare l'implementazione è il modo più semplice per farlo, allora va bene. Nel tuo esempio, potresti modificare a + b in a + a o a * b .

In alternativa, se puoi mutare leggermente l'asserzione e vedere che il test fallisce, ciò può essere piuttosto efficace nel test del test.

    
risposta data 28.01.2017 - 20:21
fonte
0

Direi, questo è il caso del tuo sistema di controllo delle versioni preferito:

  1. Registra la correzione del test, mantenendo le modifiche al codice nella directory di lavoro.
    Confida con un messaggio corrispondente Fixed test ... to expect correct output .

    Con git , questo potrebbe richiedere l'uso di git add -p se test e implementazione si trovano nello stesso file, altrimenti puoi semplicemente mettere in scena separatamente i due file.

  2. Conferma il codice di implementazione.

  3. Torna indietro nel tempo per testare il commit effettuato nel passaggio 1, assicurandoti che il test non abbia effettivamente esito .

Come vedi, in questo modo non ti affidi alle tue capacità di editing per spostare il codice di implementazione mentre controlli il tuo test fallito. Utilizzi il tuo VCS per salvare il tuo lavoro e per assicurarti che la cronologia registrata di VCS includa correttamente sia il test che fallisce.

    
risposta data 01.02.2017 - 23:07
fonte

Leggi altre domande sui tag