Quanto sono piccoli i tuoi piccoli passi nel TDD?

36

Oggi stavamo formando TDD e abbiamo trovato il seguente punto di incomprensione.

L'attività è per l'input "1,2" restituisce la somma dei numeri che è 3. Quello che ho scritto (in C #) era:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]); //task said we have two numbers and input is correct

Ma altri ragazzi hanno preferito farlo in un altro modo. Innanzitutto, per l'input "1,2" hanno aggiunto il seguente codice:

if (input == "1,2")
   return 3;

Quindi hanno introdotto un altro test per l'input "4,5" e l'implementazione modificata:

if (input == "1,2")
   return 3;
else if (input == "4,5")
   return 9;

E in seguito hanno detto "Ok, ora vediamo lo schema" e implementato ciò che inizialmente ho fatto.

Penso che il secondo approccio si adatti meglio alla definizione TDD ma ... dovremmo essere così severi al riguardo? Per me va bene saltare piccoli passi banali e combinarli in "twinsteps" se sono abbastanza sicuro che non salterò nulla. Mi sbaglio?

Aggiornamento. Ho fatto un errore non chiarendo che non era il primo test. C'erano già alcuni test quindi "return 3" in realtà non era la parte di codice più semplice per soddisfare il requisito.

    
posta SiberianGuy 22.09.2011 - 09:05
fonte

9 risposte

30

Scrivi il codice più semplice che fa passare i test.

Nessuno di voi ha fatto ciò, per quanto posso vedere.

Passaggio 1 del bambino

Test: per l'input "1,2" restituisce la somma dei numeri che è 3

Fai fallire il test:

throw NotImplementedException();

Esegui il test di prova:

return 3;

Baby Step 2.

Test: per l'input "1,2" restituisce la somma dei numeri, che è 3

Test: per l'input "4,5" restituisce la somma dei numeri, che è 9

Il secondo test fallisce, quindi fallo passare:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]);

(Più semplice di un elenco di se ... ritorno)

In questo caso si può certamente sostenere l'Implementazione ovvia, ma se si parla di fare rigorosamente in piccoli passi, questi sono i passaggi corretti, IMO.

L'argomento è che se non scrivi il secondo test, alcune scintille luminose potrebbero venire in seguito e "refactoring" il codice da leggere:

return input.Length; # Still satisfies the first test

E, senza prendere entrambi i passaggi, non hai mai fatto diventare rosso il secondo test (il che significa che il test stesso è sospetto).

    
risposta data 22.09.2011 - 09:43
fonte
49

Penso che la seconda via sia una mente incredibilmente stupida. Vedo il valore nel fare passi abbastanza piccoli, ma scrivere quei piccoli passi da zigote (non posso nemmeno chiamarli bambini) è solo una cosa asinina e una perdita di tempo. Soprattutto se il problema originale che stai risolvendo è già molto piccolo da solo.

So che si sta allenando e si tratta più di mostrare il principio, ma penso che questi esempi facciano il TDD più male che bene. Se vuoi mostrare il valore dei piccoli passi, usa almeno un problema in cui è presente un valore.

    
risposta data 22.09.2011 - 09:17
fonte
19

Kent Beck lo tratta nel suo libro, Test Driven Development: By Example.

Il tuo esempio indica un ovvia implementazione " - si desidera restituire la somma di due valori di input e questo è un algoritmo piuttosto semplice da raggiungere. Il tuo contro-esempio cade in 'finto fino a quando non lo fai' (anche se un caso molto semplice).

Ovviamente l'implementazione può essere molto più complicata di questa, ma fondamentalmente prende il via quando le specifiche per un metodo sono piuttosto strette - ad esempio, restituisce una versione con codifica URL di una proprietà di classe - non è necessario perdere tempo con un sacco di codifiche false.

Una routine di connessione al database, d'altro canto, avrebbe bisogno di un po 'più di riflessione e test, quindi non c'è un'implementazione ovvia (anche se potresti averne già scritto più volte su altri progetti).

Dal libro:

When i use TDD in practice, I commonly shift between these two modes of implementation, When everything is going smoothly and I know what to type, I put in Obvious Implementation after Obvious Implementation (running the tests each time to ensure that what's obvious to me is still obvious to the computer). As soon as I get an unexpected red bar, I back up, shift to faking implementations, and refactor to the right code. When my confidence returns, I go back to Obvious Implementations.

    
risposta data 22.09.2011 - 09:28
fonte
17

Vedo questo seguendo la lettera della legge, ma non il suo spirito.

I tuoi piccoli passi dovrebbero essere:

As simple as possible, but no simpler.

Inoltre, il verbo nel metodo è sum

if (input == "1,2")
   return 3;

non è una somma, è un test per input specifici.

    
risposta data 22.09.2011 - 14:50
fonte
4

Per me sembra giusto combinare diversi passaggi di implementazione banali in uno leggermente meno banale - lo faccio sempre anche io. Non credo che si debba fare un religioso per seguire TDD ogni volta che si tratta della lettera.

OTOH questo vale solo per passi davvero banali come l'esempio precedente. Per qualcosa di più complesso, che non riesco a tenere per intero nella mia mente e / o dove non sono sicuro al 100% circa il risultato, preferisco fare un passo alla volta.

    
risposta data 22.09.2011 - 09:17
fonte
1

Quando ci si avvicina per la prima volta a TDD, la dimensione dei passaggi può essere un problema di confusione, come illustrato da questa domanda. Una domanda che mi sono spesso posta quando ho iniziato a scrivere applicazioni testate è stata; Il test che sto scrivendo aiuta a guidare lo sviluppo delle mie applicazioni? Questo può sembrare un aspetto banale e non correlato ad alcuni, ma resta un attimo lì con me per un momento.

Ora, quando ho deciso di scrivere qualsiasi applicazione, solitamente inizierò con un test. Quanto di un passo che prova è in gran parte relativo alla mia comprensione di ciò che sto cercando di fare. Se penso di avere più o meno il comportamento di una classe nella mia testa, allora il passo sarà grande. Se il problema che sto cercando di risolvere è molto meno chiaro allora il passo potrebbe essere semplicemente che so che ci sarà un metodo chiamato X e che restituirà Y. A questo punto il metodo non avrà nemmeno alcun parametro e c'è la possibilità che il nome del metodo e il tipo di ritorno cambino. In entrambi i casi i test stanno guidando il mio sviluppo. Mi stanno dicendo cose sulla mia domanda:

Questa classe che ho nella mia testa funzionerà davvero?

o

Come diavolo ho intenzione di fare questa cosa?

Il punto è che posso passare da grandi passi a piccoli passi in un batter d'occhio. Ad esempio se un grande passo non funziona e non riesco a vedere un modo ovvio intorno a esso passerò a un passo più piccolo. Se non funziona, passerò a un passo ancora più piccolo. Poi ci sono altre tecniche come la triangolazione se mi blocco davvero.

Se come me sei uno sviluppatore e non un tester, allora il punto di usare TDD non è scrivere test ma scrivere codice. Non impiccarti a scrivere un sacco di piccoli test se non ti danno alcun vantaggio.

Spero ti sia piaciuto il tuo allenamento con il TDD. IMHO se più persone fossero testate infette allora il mondo sarebbe un posto migliore:)

    
risposta data 22.09.2011 - 11:00
fonte
1

In un manuale sui test delle unità, leggo lo stesso approccio (passaggi che sembrano davvero minuscoli) e come risposta alla domanda "quanto piccoli dovrebbero essere" qualcosa che mi è piaciuto, che è stato (parafrasato) in questo modo:

Riguarda quanto sei sicuro che i passaggi funzionino. Puoi fare grandi passi se vuoi. Ma provaci per un po 'di tempo e troverai molta confidenza nei luoghi che dai per scontato. Quindi, i test ti aiutano a costruire una fiducia basata sui fatti.

Quindi, forse il tuo collega è solo un po 'timido:)

    
risposta data 22.09.2011 - 14:18
fonte
1

Il punto non è che l'implementazione del metodo è irrilevante, a patto che i test abbiano successo? Estendere i test fallirà più rapidamente nel secondo esempio, ma può essere fatto fallire in entrambi i casi.

    
risposta data 22.09.2011 - 15:14
fonte
1

Sono d'accordo con le persone che dicono che nemmeno l'implementazione è la più semplice.

Il motivo per cui la metodologia è così severa è che ti obbliga a scrivere quanti più test pertinenti possibili. Restituire un valore costante per un test case e chiamarlo un passaggio va bene perché forze devi tornare indietro e specificare cosa vuoi veramente per ottenere qualcosa di diverso dalle assurdità dal tuo programma. L'uso di un caso così banale è di sparare a un certo punto per alcuni aspetti, ma il principio è che gli errori si insinuano nelle lacune delle specifiche quando si tenta di fare "troppo" e si riduce il requisito all'implementazione più semplice possibile. il test deve essere scritto per ogni aspetto unico del comportamento che si desidera effettivamente.

    
risposta data 22.09.2011 - 18:02
fonte

Leggi altre domande sui tag