In TDD, se scrivo un caso di test che passa senza modificare il codice di produzione, che cosa significa?

15

Queste sono le regole di Robert C. Martin per TDD :

  • Non sei autorizzato a scrivere alcun codice di produzione a meno che non lo sia effettuare un test pass
  • Non ti è permesso scrivere più di un test unitario di quanto sia sufficiente a fallire; e fallimenti di compilazione sono fallimenti.
  • Non ti è permesso scrivere più codice di produzione di quanto sia sufficiente per superare il test dell'unità fallita.

Quando scrivo un test che sembra utile ma passa senza modificare il codice di produzione:

  1. Significa che ho fatto qualcosa di sbagliato?
  2. Dovrei evitare di scrivere tali test in futuro se può essere aiutato?
  3. Devo lasciare il test lì o rimuoverlo?

Nota: stavo cercando di porre questa domanda qui: Posso iniziare con un test delle unità che passa? Ma fino ad ora non ero in grado di articolare la domanda abbastanza bene.

    
posta Daniel Kaplan 20.03.2013 - 23:58
fonte

5 risposte

21

Si dice che non è possibile scrivere il codice di produzione a meno che non si riesca a far passare un test di unità in errore, non che non si possa scrivere un test che passi dall'inizio. L'intento della regola è di dire "Se hai bisogno di modificare il codice di produzione, assicurati di scrivere o modificare prima un test per esso".

A volte scriviamo test per dimostrare una teoria. Il test passa e questo smentisce la nostra teoria. Quindi non rimuoviamo il test. Tuttavia, potremmo (sapendo di avere il supporto del controllo del codice sorgente) interrompere il codice di produzione, per assicurarci di capire perché è passato quando non ci aspettavamo di farlo.

Se risulta essere un test valido e corretto, e non sta duplicando un test esistente, lascialo lì.

    
risposta data 21.03.2013 - 00:05
fonte
13

Significa che:

  1. Hai scritto il codice di produzione che soddisfa la funzione desiderata senza prima scrivere il test (una violazione di "TDD religioso") o
  2. La funzione di cui hai bisogno sembra essere già soddisfatta dal codice di produzione e stai solo scrivendo un altro test unitario per coprire quella funzione.

L'ultima situazione è più comune di quanto si possa pensare. Come esempio completamente specioso e banale (ma comunque illustrativo), diciamo che hai scritto il seguente test unitario (pseudocodice, perché sono pigro):

public void TestAddMethod()
{
    Assert.IsTrue(Add(2,3) == 5);
}

Perché tutto ciò di cui hai veramente bisogno è il risultato del 2 e del 3 sommati insieme.

Il tuo metodo di implementazione sarebbe:

public int add(int x, int y)
{
    return x + y;
}

Ma diciamo che ora devo aggiungere 4 e 6 insieme:

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

Non ho bisogno di riscrivere il mio metodo, perché copre già il secondo caso.

Ora diciamo che ho scoperto che la mia funzione Aggiungi ha davvero bisogno di restituire un numero che ha un certo limite, diciamo 100. Posso scrivere un nuovo metodo per testarlo:

public void TestAddMethod3()
{
    Assert.IsTrue(Add(100,100) == 100);
}

E questo test ora fallirà. Ora devo riscrivere la mia funzione

public int add(int x, int y)
{
    var a = x + y;
    return a > 100 ? 100 : a;
}

per farlo passare.

Il buon senso impone che se

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

passa, non deliberatamente fai fallire il tuo metodo solo in modo da poter avere un test in errore in modo che tu possa scrivere un nuovo codice per fare quel test.

    
risposta data 21.03.2013 - 00:03
fonte
2

Il tuo test passa ma non sbagli. Penso che sia successo perché il codice di produzione non è TDD dall'inizio.

Supponiamo che sia canonico (?) TDD. Non esiste un codice di produzione ma alcuni casi di test (che ovviamente falliscono sempre). Aggiungiamo il codice di produzione per passare. Quindi fermati qui per aggiungere altri casi di test di errore. Ancora una volta aggiungi il codice di produzione da passare.

In altre parole, il test potrebbe essere una sorta di test di funzionalità, non un semplice test dell'unità TDD. Queste sono sempre risorse preziose per la qualità del prodotto.

Personalmente non mi piacciono queste regole totalitarie e disumane;

    
risposta data 21.03.2013 - 08:07
fonte
2

In realtà lo stesso problema è emerso in un dojo la scorsa notte.

Ho fatto una rapida ricerca su di esso. Questo è quello che mi è venuto in mente:

Fondamentalmente non è vietato esplicitamente dalle regole TDD. Forse sono necessari alcuni test aggiuntivi per dimostrare che una funzione funziona correttamente per un input generalizzato. In questo caso la pratica del TDD viene lasciata da parte solo per un po '. Nota che lasciare la pratica del TDD a breve non significa necessariamente violare le regole TDD finché non è stato aggiunto alcun codice di produzione nel frattempo.

È possibile scrivere test aggiuntivi a condizione che non siano ridondanti. Una buona pratica sarebbe fare test di partizionamento di classe di equivalenza. Ciò significa che vengono testati i casi limite e almeno un caso interno per ogni classe di equivalenza.

Un problema che potrebbe verificarsi con questo approccio, tuttavia, è che se i test passano dall'inizio non è possibile garantire che non vi siano falsi positivi. Significa che potrebbero passare dei test perché i test non sono implementati correttamente e non perché il codice di produzione funziona correttamente. Per evitare ciò, il codice di produzione dovrebbe essere leggermente modificato per interrompere il test. Se questo rende il test fallito, il test è molto probabilmente implementato correttamente e il codice di produzione può essere modificato per far passare nuovamente il test.

Se vuoi semplicemente praticare il TDD rigoroso, potresti non scrivere nessun test aggiuntivo che passi dall'inizio. D'altra parte in un ambiente di sviluppo aziendale si dovrebbe in effetti lasciare la pratica del TDD se sembra che test aggiuntivi siano utili.

    
risposta data 28.01.2015 - 11:29
fonte
0

Un test che passa senza modificare il codice di produzione non è intrinsecamente sbagliato e spesso è necessario per descrivere un requisito aggiuntivo o un caso limite. Finché il tuo test "sembra utile", come dici tu stesso, tienilo.

Dove ti trovi nei guai è quando scrivi un test già superato come rimpiazzo per capire effettivamente lo spazio del problema.

Possiamo immaginare a due estremi: un programmatore che scrive un gran numero di test "nel caso in cui" uno cogli un bug; e un secondo programmatore che analizza attentamente lo spazio del problema prima di scrivere un numero minimo di test. Supponiamo che entrambi stiano cercando di implementare una funzione di valore assoluto.

Il primo programmatore scrive:

assert abs(-88888) == 88888
assert abs(-12345) == 12345
assert abs(-5000) == 5000
assert abs(-32) == 32
assert abs(46) == 46
assert abs(50) == 50
assert abs(5001) == 5001
assert abs(999999) == 999999
...

Il secondo programmatore scrive:

assert abs(-1) == 1
assert abs(0) == 0
assert abs(1) == 1

L'implementazione del primo programmatore potrebbe comportare:

def abs(n):
    if n < 0:
        return -n
    elif n > 0:
        return n

L'implementazione del secondo programmatore potrebbe comportare:

def abs(n):
    if n < 0:
        return -n
    else:
        return n

Tutti i test passano, ma il primo programmatore non ha solo scritto diversi test ridondanti (rallentando inutilmente il loro ciclo di sviluppo), ma ha anche fallito nel test di un caso limite ( abs(0) ).

Se ti ritrovi a scrivere test che passano senza modificare il codice di produzione, chiediti se i test stanno davvero aggiungendo valore o se è necessario dedicare più tempo alla comprensione dello spazio del problema.

    
risposta data 08.10.2016 - 17:42
fonte

Leggi altre domande sui tag