Perché non scrivere tutti i test contemporaneamente quando si fa TDD?


55

Il ciclo rosso - verde - refattore per TDD è ben definito e accettato. Scriviamo un test unitario fallito e lo facciamo passare nel modo più semplice possibile. Quali sono i vantaggi di questo approccio rispetto alla scrittura di molti test unitari non riusciti per una classe e farli passare tutti in una volta.

La suite di test ti protegge ancora dalla scrittura di codice errato o da errori nella fase di refactoring, quindi qual è il danno? A volte è più facile scrivere prima tutti i test per una classe (o un modulo) come una forma di "discarica del cervello" per scrivere rapidamente tutto il comportamento previsto in una volta sola.


20
Fai ciò che funziona meglio per te (dopo un po 'di sperimentazione). Seguire ciecamente il dogma non è mai una buona cosa.
Michael Borgwardt,

6
Oserei dire che scrivere tutti i tuoi test contemporaneamente è come scrivere tutto il tuo codice app in una volta.
Michael Haren,

1
@MichaelHaren Tutti i test per una classe (o modulo funzionale), scusate la confusione
RichK

3
Affrontare il problema della "discarica del cervello": a volte ci sono punti nel test / codifica quando ti rendi conto della necessità di diversi test di input specifici diversi, e c'è la tendenza a voler capitalizzare sulla chiarezza di quella realizzazione prima di distrarti con il minuzie della codifica. Di solito lo gestisco mantenendo un elenco separato (ad es. Mylyn), oppure con un elenco di commenti nella classe Test di cose diverse che voglio ricordare di provare (ad es. // test null case). Tuttavia, ancora codice solo un test alla volta, e invece procedo sistematicamente verso il basso nell'elenco.
Sam Goldberg,

1
bene non so perché nessuno ne abbia parlato, ma NON PUOI scrivere tutti i test in una volta. Scrivere tutti i test prima è esattamente lo stesso di fare BDUF . E cosa ci ha insegnato la storia su BDUF? Non funziona quasi mai.
Songo,

Risposte:


50

La progettazione guidata dai test consiste nel mettere correttamente l' API , non nel codice.

Il vantaggio di scrivere prima i test falliti più semplici è che ottieni la tua API (che essenzialmente stai progettando al volo) il più semplice possibile. In anticipo.

Eventuali usi futuri (quali sono i prossimi test che scrivi) andranno dal design semplice iniziale, invece di un design non ottimale per far fronte a casi più complessi.


Punti eccellenti! A volte siamo così immersi nel testare il codice che a volte ignoriamo quanto possano essere importanti l'API e il modello di dominio prima ancora di scrivere il tuo primo test.
maple_shaft

+1 per aver effettivamente affrontato l'intento di Test Driven Development.
Joshua Drake,

76

Quando scrivi un test, ti concentri su una cosa.
Con molti test diffondi la tua attenzione su molti compiti, quindi non è una buona idea.


8
Chi vorrebbe sottovalutare questo ?!
CaffGeek,

6
@Chad Non ero il voto negativo, ma credo che questa risposta manchi l'ovvio. Lo sviluppo Test Driven consiste nell'utilizzare i Test per guidare la progettazione del codice. Scrivi il test singolarmente al fine di evolvere il design, non solo per la testabilità. Se si trattasse solo dei manufatti del test, questa sarebbe una buona risposta, ma mancano alcune informazioni cruciali.
Joshua Drake,

7
Non ho votato in negativo ma; Ci ho pensato. È una risposta troppo breve a una domanda complessa.
Mark Weston,

2
+1 per concentrarsi su una cosa alla volta, la nostra capacità di multitasking è sopravvalutata.
cctan,

È la risposta più semplice che potrebbe funzionare.
DNA,

27

Una delle difficoltà quando si scrivono test unitari è che si sta scrivendo codice e che di per sé può essere potenzialmente soggetto a errori. C'è anche la possibilità che potresti finire per dover cambiare i test in seguito a seguito di uno sforzo di refactoring mentre scrivi il tuo codice di implementazione. Con TDD, ciò significa che potresti potenzialmente finire un po 'troppo trascurato con i tuoi test e trovare te stesso che ha bisogno di riscrivere un sacco di codice di prova essenzialmente "non testato" man mano che l'implementazione matura nel corso del progetto. Un modo per evitare questo tipo di problema è semplicemente concentrarsi sul fare una sola cosa alla volta. Ciò consente di ridurre al minimo l'impatto di eventuali modifiche sui test.

Naturalmente, questo dipenderà in gran parte da come scrivi il tuo codice di prova. Stai scrivendo un test unitario per ogni singolo metodo o stai scrivendo test incentrati su caratteristiche / requisiti / comportamenti? Un altro approccio potrebbe essere quello di utilizzare un approccio basato sul comportamento con un framework adeguato e concentrarsi sulla scrittura di test come se fossero specifiche. Ciò significherebbe adottare il metodo BDD o adattare il test BDD se si desidera attenersi in modo più formale al TDD. In alternativa, è possibile attenersi interamente al paradigma TDD, ma alterare il modo in cui si scrivono i test in modo che invece di concentrarsi interamente sui metodi di test singolarmente, testare i comportamenti più in generale come mezzo per soddisfare le specifiche delle caratteristiche dei requisiti che si stanno implementando.

Indipendentemente dall'approccio specifico che segui, in tutti i casi che ho descritto sopra stai usando un approccio test-first, quindi mentre potresti essere tentato di scaricare semplicemente il tuo cervello in una deliziosa suite di test, vuoi anche combattere il la tentazione di fare più di quanto sia assolutamente necessario. Ogni volta che sto per iniziare una nuova suite di test, comincio a ripetere YAGNI a me stesso e talvolta lo inserisco anche in un commento nel mio codice per ricordarmi di rimanere concentrato su ciò che è immediatamente importante e di fare solo il minimo necessario per soddisfare il requisiti della funzionalità che sto per implementare. Attenersi a Red-Green-Refactor aiuta a garantire che lo farai.


Sono felice che tu abbia sottolineato la distinzione tra il modo in cui si scrive il loro codice di prova. Ad alcuni piace scrivere un singolo test dell'unità master che copra ogni possibilità realistica di input per una singola funzione o metodo. Altri adottano un approccio più BDD con i loro test unitari. Questa distinzione è importante quando si determina se è importante scrivere un'intera serie di test è necessariamente una cattiva pratica o meno. Ottima intuizione!
maple_shaft

17

Penso che facendo questo, ti perderai il processo di TDD. Scrivendo tutti i tuoi test all'inizio non stai davvero attraversando il processo di sviluppo usando TDD. Stai semplicemente indovinando in anticipo quali test ti serviranno. Questa sarà una serie di test molto diversi da quelli che finisci per scrivere se li fai uno alla volta mentre sviluppi il tuo codice. (A meno che il tuo programma non sia di natura banale.)


1
La maggior parte delle applicazioni aziendali e aziendali ha una natura tecnicamente banale e, visto che la maggior parte delle applicazioni sono business e enterprise, la maggior parte delle applicazioni è quindi anche banale per natura.
maple_shaft

5
@maple_shaft - la tecnologia può essere banale, ma le regole aziendali non lo sono. Prova a creare un'app per 5 manager, tutti con esigenze diverse e rifiuta di ascoltare alcune BS sul tuo design minimalista, semplice, elegante e poco costoso.
JeffO,

5
@JeffO 1) Non è BS. 2) Un elegante design minimalista richiede buone capacità di sviluppo software. 3) La capacità di mitigare i requisiti di 5 diversi manager che non hanno più di 5 minuti a settimana da perdere con te e comunque realizzare un design minimalista richiede un eccellente sviluppatore di software. Consiglio del professionista: lo sviluppo del software non è solo una capacità di programmazione, è negoziazione, conversazione e assunzione della proprietà. Devi essere un cane Alpha e mordere qualche volta.
maple_shaft

1
Se ho capito bene, questa risposta è chiedere la domanda.
Konrad Rudolph,

1
@maple_shaft Penso che fosse quello a cui Jeff O stava arrivando con il suo commento, no?
ZweiBlumen

10

Faccio "scrivere" tutti i test che riesco a pensare in anticipo mentre "brain storming", tuttavia scrivo ogni test come un singolo commento che descrive il test.

Quindi converto un test in codice e faccio il lavoro in modo che venga compilato e passato . Spesso decido che non ho bisogno di tutti i test che pensavo di aver fatto, o ho bisogno di diversi test, queste informazioni provengono solo dalla scrittura del codice per far passare i test.

Il problema è che non puoi scrivere un test nel codice fino a quando non hai creato il metodo e le classi che test, altrimenti altrimenti otterrai molti errori del compilatore che ti impediscono di lavorare su un singolo test alla volta.

Ora, se stai usando un sistema come il flusso di specifiche quando i test sono scritti in "inglese", potresti voler convincere i clienti ad accettare una serie di test mentre hai il loro tempo, piuttosto che creare solo un singolo test.


1
Sì, pur concordando con le risposte di cui sopra che evidenziano prima i problemi con la codifica di tutti i test, trovo molto utile scaricare la mia comprensione generale di come l'attuale metodo dovrebbe comportarsi come un insieme di descrizioni dei test senza alcun codice. Il processo di scrittura di questi tende a chiarire se comprendo completamente ciò che è richiesto dal codice che sto per scrivere e se ci sono casi limite a cui non ho pensato. Mi trovo molto più a mio agio nel codificare il primo test e poi nel superarlo dopo aver delineato la mia "panoramica" su come dovrebbe funzionare il metodo.
Mark Weston,

10

L'idea alla base di TDD è iterazioni rapide.

Se hai grandi quantità di test che devono essere scritti prima di dover scrivere il tuo codice, è difficile riformattare iterativamente il tuo codice.

Senza un semplice refactoring del codice perdi molti dei vantaggi del TDD.


5

Nella mia (limitata) esperienza con TDD, posso dirti che ogni volta che ho infranto la disciplina di scrivere un test alla volta, le cose sono andate male. È una trappola facile da cadere. "Oh, quel metodo è banale", pensi a te stesso, "quindi eliminerò questi due altri test correlati e continuerò a muovermi." Beh, indovina un po '? Niente è così banale come sembra. Ogni volta che sono caduto in questa trappola, ho finito per eseguire il debug di qualcosa che pensavo fosse facile, ma si è rivelato avere strani casi angolari. E da quando ho continuato a scrivere diversi test contemporaneamente, è stato un sacco di lavoro per rintracciare dove si trovava il bug.

Se hai bisogno di una scarica di informazioni sul cervello, hai molte opzioni:

  • Lavagna
  • Storie degli utenti
  • Commenti
  • Buona vecchia carta e penna

Si noti che da nessuna parte in questo elenco è il compilatore. :-)


5

Stai assumendo di sapere come apparirà il tuo codice prima di scriverlo. TDD / BDD è tanto un processo di progettazione / scoperta quanto un processo di controllo qualità. Per una determinata funzionalità si scrive il test più semplice che verificherebbe che la funzionalità sia soddisfatta (a volte ciò potrebbe richiedere diversi a causa della complessità di una funzione). Quel primo test che scrivi è carico di ipotesi su come apparirà il codice di lavoro. Se scrivi l'intera suite di test prima di scrivere la prima riga di codice per supportarla, stai facendo una litania di ipotesi non verificate. Invece, scrivi un'ipotesi e verificala. Quindi scrivi il prossimo. Nel processo di verifica del presupposto successivo, potresti semplicemente infrangere un presupposto precedente, quindi devi tornare indietro e cambiare il primo presupposto in modo che corrisponda alla realtà o cambiare la realtà in modo che il primo presupposto sia ancora valido.

Pensa a ogni test unitario che scrivi come teoria in un quaderno scientifico. Man mano che compili il quaderno dimostrerai le tue teorie e ne formerai di nuove. A volte la dimostrazione di una nuova teoria smentisce una teoria precedente, quindi è necessario risolverla. È più facile provare una teoria alla volta piuttosto che provare a dire 20 in una volta.


TDD presume che tu sappia come apparirà il tuo codice prima di scriverlo, solo in pezzi più piccoli.
Michael Shaw,

4

TDD è un approccio altamente iterativo, che (nella mia esperienza) si adatta meglio ai modi di sviluppo del mondo reale. Di solito la mia implementazione prende forma gradualmente durante questo processo e ogni passaggio può portare ulteriori domande, approfondimenti e idee per i test. Questo è l'ideale per mantenere la mia mente concentrata sul compito reale ed è molto efficiente perché ho solo bisogno di mantenere un numero limitato di cose nella memoria a breve termine in qualsiasi momento. Questo a sua volta riduce la possibilità di errori.

La tua idea è fondamentalmente un approccio Big Test Up Front, che IMHO è più difficile da gestire e può diventare più dispendioso. Che cosa succede se ti rendi conto a metà del tuo lavoro che il tuo approccio non è buono, la tua API è difettosa e devi ricominciare da capo o utilizzare invece una libreria di terze parti? Quindi gran parte del lavoro fatto per scrivere i tuoi test in anticipo diventa uno sforzo sprecato.

Detto questo, se questo funziona per te, va bene. Posso immaginare che se lavori da una specifica tecnica dettagliata e fissa, su un dominio con cui sei intimamente esperto e / o su un'attività abbastanza piccola, potresti avere la maggior parte o tutti i casi di test necessari pronti e la tua implementazione chiara fin da la partenza. Quindi potrebbe avere senso iniziare scrivendo tutti i test contemporaneamente. Se la tua esperienza è che questo ti rende più produttivo a lungo termine, non devi preoccuparti troppo dei libri di regole :-)


4

Oltre a pensare solo a una cosa, un paradigma di TDD è quello di scrivere il minor codice possibile per superare il test. Quando scrivi un test alla volta, è molto più facile vedere il percorso per scrivere il codice sufficiente per far passare quel test. Con un'intera serie di test da superare, non si arriva al codice in piccoli passi ma si deve fare un grande salto per farli passare tutti in una volta.

Ora, se non ti limiti a scrivere il codice per farli passare tutti "in una volta", ma piuttosto scrivi il codice sufficiente per passare un test alla volta, potrebbe ancora funzionare. Dovresti avere più disciplina non solo per andare avanti e scrivere più codice di quello che ti serve, però. Una volta iniziato quel percorso, ti lasci aperto a scrivere più codice di quanto descrivono i test, che può essere non testato , almeno nel senso che non è guidato da un test e forse nel senso che non è necessario (o esercitato) da qualsiasi test.

Abbassare ciò che il metodo dovrebbe fare, come commenti, storie, una specifica funzionale, ecc., È perfettamente accettabile. Aspetterei di tradurli in test uno alla volta però.

L'altra cosa che puoi perdere scrivendo i test tutti in una volta è il processo di pensiero attraverso il quale passare un test può spingerti a pensare ad altri casi di test. Senza una banca di test esistenti, è necessario pensare al prossimo caso di test nel contesto dell'ultimo test superato. Come ho detto, avere una buona idea di cosa dovrebbe fare il metodo è molto buono, ma molte volte mi sono ritrovato a trovare nuove possibilità che non avevo considerato a priori, ma che si sono verificate solo nel processo di scrittura del test. C'è il pericolo che potresti perdere questi a meno che tu non abbia l'abitudine specifica di pensare a quali nuovi test posso scrivere che non ho già.


3

Ho lavorato su un progetto in cui gli sviluppatori che hanno scritto i test (non riusciti) erano diversi dagli sviluppatori che implementavano il codice necessario per farli passare e l'ho trovato davvero efficace.

In tal caso, solo i test relativi all'attuale iterazione sono stati scritti una volta. Quindi ciò che suggerisci è perfettamente possibile in quel tipo di scenario.


2
  • Quindi provi a concentrarti su troppe cose alla volta.
  • Durante l'implementazione per superare tutti i test non hai una versione funzionante della tua applicazione. Se devi implementare molto, non avrai una versione funzionante per molto tempo.

2

Il ciclo Red-Green-Refactor è una lista di controllo destinata agli sviluppatori che non conoscono TDD. Direi che è una buona idea seguire questa lista di controllo fino a quando non sai quando seguirla e quando puoi romperla (cioè fino a quando non sai di non dover porre questa domanda su StackOverflow :)

Avendo fatto TDD per quasi un decennio, posso dirti che raramente, se mai, scrivo molti test falliti prima di scrivere il codice di produzione.


1

Stai descrivendo BDD, dove alcuni stakeholder esterni hanno una specifica eseguibile. Ciò può essere utile se esiste una specifica anteriore predeterminata (ad esempio una specifica di formato, uno standard industriale o in cui il programmatore non è l'esperto di dominio).

L'approccio normale è quindi quello di coprire gradualmente sempre più test di accettazione, che sono i progressi visibili al project manager e al cliente.

Di solito hai questi test specificati ed eseguiti in un framework BDD come Cucumber, Fitnesse o alcuni di questi.

Tuttavia, questo non è qualcosa che si confonde con i test unitari, che sono molto più vicini ai dettagli di implementazione grintosi con molti casi limite relativi all'API, problemi di inizializzazione ecc. Fortemente focalizzati sull'elemento in prova , che è un artefatto di implementazione .

La disciplina del refattore rosso-verde ha molti vantaggi e l'unico vantaggio che puoi sperare digitandoli in anticipo è di pareggiare.


1

Un test alla volta: il vantaggio principale è concentrarsi su una cosa. Pensa al design approfondito: puoi andare in profondità e rimanere concentrato con un loop di feedback rapido. Tuttavia, potresti perdere l'ambito dell'intero problema! È in questo momento che entra in gioco il (grande) refactoring. Senza di essa TDD non funziona.

Tutti i test: l'analisi e la progettazione possono rivelare di più l'ambito del problema. Pensa al primo design. Analizzi il problema da più angolazioni e aggiungi input dall'esperienza. È intrinsecamente più difficile, ma può portare benefici interessanti - meno refactoring - se ne fai "quanto basta". Attenzione, è facile analizzare troppo e tuttavia perdere completamente il segno!

Trovo difficile raccomandare generalmente di preferire l'uno o l'altro, perché i fattori sono molti: esperienza (specialmente con lo stesso problema), conoscenza e abilità del dominio, cordialità del codice per il refactoring, complessità del problema ...

Immagino che se ci concentrassimo più strettamente sulle tipiche applicazioni aziendali, TDD con il suo approccio di prova ed errore a velocità quasi costante di solito vincerebbe in termini di efficacia.


1

Supponendo che il tuo framework di test lo supporti, ciò che suggerirei è che invece di implementare i test che vuoi testare, scrivi invece dei test descrittivi in ​​attesa che implementerai in seguito. Ad esempio, se la tua API deve eseguire foo and bar ma non biz, aggiungi il seguente codice (questo esempio è in rspec) per la tua suite di test, quindi attaccali uno per uno. Abbassi rapidamente i tuoi pensieri e puoi affrontare tutti i tuoi problemi uno per uno. Una volta superati tutti i test, saprai quando hai risolto tutti i problemi che hai avuto durante il tuo Braindump.

describe "Your API" do

  it "should foo" do
    pending "braindump from 4/2"
  end

  it "should bar" do
    pending "braindump from 4/2"
  end

  it "should not biz" do
    pending "braindump from 4/2"
  end

end
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.