Quanto sono piccoli i tuoi piccoli passi nel TDD?


37

Oggi abbiamo formato TDD e abbiamo scoperto il seguente punto di incomprensione.

Il compito è per la somma di ritorno "1,2" dei numeri che è 3. Ciò 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 diversamente. Innanzitutto, per l'ingresso "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 hanno modificato l'implementazione:

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

E dopo hanno detto "Okay, ora vediamo lo schema" e hanno implementato quello 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 da non saltare nulla. Ho sbagliato?

Aggiornare. Ho fatto un errore non chiarendo che non era il primo test. Esistevano già alcuni test, quindi "return 3" non era in realtà il codice più semplice per soddisfare il requisito.


25
Così piccolo che i miei colleghi sgorgano "Ooahhh dazso cuuuuuute"
Adel,

6
@Adel: Quasi soffocato a colazione, tastiera ora piena o sputa e briciole
Binary Worrier,

2
@Adel, per quanto riguarda i non madrelingua, è piuttosto difficile per me capire questo umorismo ma immagino che ai tuoi colleghi piaccia la domanda :)
SiberianGuy

8
@Idsa: traspone una risposta di colleghi quando viene mostrato i primi passi di un bambino "Ooahhh dazso cuuuuuute" = "Oh, è così carino" (pronunciato con una voce cantata-non-che-molto-carina), con la loro risposta quando vedono i test unitari scritti da Adel, guardando i piccoli passi dei test unitari, dicono "Oh, è così carino". Reazione ad un - reale - passi del bambino = reazione ai test unitari "piccoli passi".
Binary Worrier,

3
@Binaryworrier vorrei poterti dare dei punti concreti per dedicare del tempo a spiegare la genitorialità
Andrew T Finnell,

Risposte:


31

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

Nessuno di voi lo ha fatto, per quanto posso vedere.

Baby Step 1.

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

Fai fallire il test:

throw NotImplementedException();

Fai passare il test:

return 3;

Baby Step 2.

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

Test: per l'ingresso "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]);

(Molto più semplice di un elenco di if ... return)

In questo caso, puoi certamente sostenere l'implementazione ovvia, ma se stavi parlando di farlo rigorosamente in piccoli passi, questi sono i passaggi corretti, IMO.

L'argomentazione è che se non si scrive il secondo test, potrebbe verificarsi una scintilla luminosa in seguito e "refactor" il codice da leggere:

return input.Length; # Still satisfies the first test

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


Per quanto riguarda il tuo contributo. Esempio di lunghezza, con lo stesso successo posso immaginare un'implementazione errata e folle che non sarà colta da entrambi i test
SiberianGuy

@Idsa - Sì, assolutamente, e più test scrivi, più l'implementazione deve essere folle. input.Lengthnon è così inverosimile, specialmente se l'input per il metodo sembra essere una misura da qualche file da qualche parte e tu hai involontariamente chiamato il tuo metodo Size().
pdr,

6
+1. Per quanto riguarda l'apprendimento del TDD, questo è il modo giusto. Una volta che l'hai imparato, a volte potresti passare direttamente all'ovvia implementazione, ma per avere un'idea del TDD, questo è molto meglio.
Carl Manaster,

1
Ho una domanda relativa al "test" stesso. Scriveresti un nuovo test per l'ingresso "4,5" o modificheresti il ​​test originale?
mxmissile,

1
@mxmissile: scriverei un nuovo test. Non ci vuole molto tempo e ti ritroverai con il doppio dei test per proteggerti quando eseguirai il refactoring in seguito.
pdr,

50

Penso che il secondo modo sia la mente numericamente stupida. Vedo il valore nel fare passi abbastanza piccoli, ma scrivere quei piccoli passi di zigote (non posso nemmeno chiamarli piccoli) è semplicemente stupido 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 tali esempi facciano TDD più male che bene. Se vuoi mostrare il valore dei piccoli passi, usa almeno un problema in cui c'è del valore.


+1 e grazie per avermi fatto cercare e imparare una nuova parola (asinina)
Marjan Venema,

12
+1 per averlo definito stupidamente stupido. TDD è tutto bello e così, ma come con qualsiasi moderna tecnica di programmazione pubblicizzata, dovresti fare attenzione a non perderti.
stijn

2
"Soprattutto se il problema originale che stai risolvendo è già molto piccolo da solo." - Se l'input fosse di due inte da sommare, sarei d'accordo con questo, ma non sono convinto quando si tratta di "dividere una stringa, analizzare due ints dal risultato e aggiungerli". La maggior parte dei metodi nel mondo reale non è molto più complicata di così. In effetti, dovrebbero esserci più test a venire, per coprire casi limite come trovare due virgole, valori non interi, ecc.
pdr

4
@pdr: sono d'accordo con te che dovrebbero esserci più test per gestire i casi limite. Quando le scrivi e noti che l'implementazione deve cambiare per gestirle, lo fai sicuramente. Immagino di avere un problema con il fare passi da zigote verso il primo percorso felice, "ovvia implementazione", invece di scriverlo e andare da lì. Non vedo il valore nello scrivere un'affermazione if che ogni fibra nel mio corpo sa che sparirà nel momento successivo.
Christophe Vanfleteren,

1
@ChristopheVanfleteren: Quando Beck descrive l'implementazione ovvia, usa la somma di due pollici come esempio e lancia ancora un enorme avvertimento drammatico su come morirai di vergogna se la tua coppia / revisore può pensare a un codice più semplice che rende il test pass. Questa è una certezza assoluta se si scrive un solo test per questo scenario. Inoltre, posso pensare ad almeno tre modi "ovvi" per risolvere questo problema: dividere e aggiungere, sostituire la virgola con + e valutare o usare regex. Il punto di TDD è guidarti alla scelta corretta.
pdr,

19

Kent Beck ne parla nel suo libro Test Driven Development: By Example.

Il tuo esempio indica una " implementazione ovvia ": vuoi restituire la somma di due valori di input, e questo è un algoritmo abbastanza semplice da raggiungere. Il tuo contro-esempio rientra nel "falso finché non lo fai" (anche se un caso molto semplice).

L'implementazione ovvia può essere molto più complicata di così - ma fondamentalmente entra in gioco quando la specifica di un metodo è piuttosto rigorosa - ad esempio, restituisce una versione codificata URL di una proprietà di classe - non è necessario perdere tempo con un sacco di codifiche simulate.

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

Dal libro:

Quando pratico TDD in pratica, di solito passo tra queste due modalità di implementazione, quando tutto procede senza intoppi e so cosa digitare, inserisco la Implementazione ovvia dopo l'implementazione ovvia (eseguendo i test ogni volta per garantire ciò che è ovvio per me è ancora ovvio per il computer). Non appena ricevo una barra rossa inaspettata, eseguo il backup, passaggio a implementazioni false e refactoring sul codice giusto. Quando la mia fiducia ritorna, torno a Ovvio Implementations.


18

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

I tuoi piccoli passi dovrebbero essere:

Il più semplice possibile, ma non più semplice.

Inoltre, il verbo nel metodo è sum

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

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


4

A me sembra bene combinare diversi passaggi di implementazione banali in uno leggermente meno banale - lo faccio sempre. Non credo che si debba diventare religiosi nel seguire il TDD ogni volta che si arriva alla lettera.

OTOH questo vale solo per passaggi davvero banali come nell'esempio sopra. Per qualcosa di più complesso, che non riesco a ricordare completamente in una volta e / o in cui non sono sicuro al 110% del risultato, preferisco fare un passo alla volta.


1

Quando si inizia per la prima volta lungo la strada del TDD, la dimensione dei passaggi può essere un problema confuso, come illustra questa domanda. Una domanda che mi ponevo spesso quando ho iniziato a scrivere applicazioni test-driven era; Il test che sto scrivendo aiuta a guidare lo sviluppo delle mie applicazioni? Questo può sembrare banale e non correlato ad alcuni, ma rimanere lì con me per un momento.

Ora, quando ho deciso di scrivere qualsiasi applicazione, di solito inizierò con un test. Quanto di un passo di quel test è in gran parte legato alla mia comprensione di ciò che sto cercando di fare. Se penso di avere praticamente il comportamento di una classe nella mia testa, il passo sarà grande. Se il problema che sto cercando di risolvere è molto meno chiaro, il passaggio 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 restituito cambino. In entrambi i casi i test stanno guidando il mio sviluppo. Mi stanno dicendo cose sulla mia applicazione:

Questa classe che ho in 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 vederlo in modo ovvio, passerò a un passo più piccolo. Se non funziona, passerò a un passaggio ancora più piccolo. Poi ci sono altre tecniche come la triangolazione se rimango davvero bloccato.

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

Spero che ti sia piaciuto allenarti con TDD. IMHO se più persone fossero state infettate dal test, il mondo sarebbe un posto migliore :)


1

In un'anteprima del test unitario ho letto lo stesso approccio (passaggi che sembrano davvero molto piccoli) e come risposta alla domanda "quanto piccoli dovrebbero essere" qualcosa che mi è piaciuto, che è stato (parafrasato) in questo modo:

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

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


1

Il punto è che l'implementazione del metodo è irrilevante, purché i test abbiano successo? L'estensione dei test fallirà più rapidamente nel secondo esempio, ma può essere fatta fallire in entrambi i casi.


1
È irrilevante se non ti interessa perdere tempo
SiberianGuy,

1

Sono d'accordo con la gente dicendo che non è nemmeno la più semplice implementazione.

Il motivo per cui la metodologia è così rigorosa è che ti obbliga a scrivere quanti più test pertinenti possibili. Restituire un valore costante per un caso di test e chiamarlo un passaggio va bene perché ti costringe a tornare indietro e specificare ciò che vuoi davvero per ottenere qualsiasi cosa diversa dalle sciocchezze dal tuo programma. L'utilizzo di un caso così insignificante ti spara sotto il piede per alcuni aspetti, ma il principio è che gli errori si insinuano nelle lacune nelle tue specifiche quando provi a fare "troppo" e rendendo il requisito per l'implementazione più semplice possibile assicura che un test deve essere scritto per ogni aspetto unico del comportamento che si desidera effettivamente.


Ho aggiunto un aggiornamento su "restituire un valore costante"
SiberianGuy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.