Come fare Test Driven Development


15

Ho solo 2+ anni di esperienza nello sviluppo di applicazioni. In quei due anni il mio approccio allo sviluppo era il seguente

  1. Analizzare i requisiti
  2. Componente / oggetti Identity Core, funzioni richieste, comportamento, processo e loro vincoli
  3. Crea classi, relazioni tra loro, vincoli sul comportamento e gli stati degli oggetti
  4. Creare funzioni, elaborare con vincoli comportamentali secondo i requisiti
  5. Test manuale dell'applicazione
  6. Se i requisiti cambiano, modificare il componente / le funzioni, quindi testare manualmente l'applicazione

Recentemente sono stato introdotto al TDD e ritengo che questo sia un ottimo modo per fare lo sviluppo poiché il codice sviluppato ha forti ragioni per esistere e molti problemi post-distribuzione sono mitigati.

Ma il mio problema è che non sono in grado di creare prima i test, piuttosto sto identificando i componenti e sto solo scrivendo dei test prima di scrivere i componenti. la mia domanda è

  1. Lo sto facendo è giusto? Altrimenti cosa devo cambiare esattamente
  2. C'è un modo per identificare se il test che hai scritto è sufficiente?
  3. È buona norma scrivere test per funzionalità molto semplici che potrebbero essere equivalenti a 1 + 1 = 2 o è solo un overplay?
  4. È utile modificare la funzionalità e testare di conseguenza se i requisiti cambiano?

2
"Sto identificando i componenti e sto solo scrivendo test per loro prima di scrivere effettivamente i componenti.": Lo trovo corretto: prima identifichi l'architettura grossolana del tuo sistema e poi inizi a scrivere codice. Durante la codifica (TDD) si elaborano i dettagli dei singoli componenti e si scoprono eventualmente problemi con la propria architettura che è possibile risolvere lungo il percorso. Ma trovo OK che non inizi a scrivere codice senza alcuna analisi precedente.
Giorgio,

Potresti anche provare a fare test automatizzati di unità / integrazione senza fare TDD. I due sono spesso confusi, ma non sono la stessa cosa.
Andres F.

Risposte:


19

Lo sto facendo è giusto? Altrimenti cosa devo cambiare esattamente

E 'difficile dire proprio da quel breve descrizione, ma ho il sospetto che, no, si sta non facendo bene. Nota: non sto dicendo che quello che stai facendo non funzioni o sia in qualche modo negativo, ma non stai facendo TDD. La "D" centrale significa "Driven", i test guidano tutto, il processo di sviluppo, il codice, il design, l'architettura, tutto .

I test ti dicono cosa scrivere, quando scriverlo, cosa scrivere dopo, quando smettere di scrivere. Ti dicono il design e l'architettura. (Il design e l'architettura emergono dal codice attraverso il refactoring.) TDD non riguarda i test. Non si tratta nemmeno di scrivere prima i test: TDD significa lasciare che i test ti guidino, scriverli prima è solo un prerequisito necessario per questo.

Non importa se in realtà scrivi il codice o se lo hai completamente chiarito: stai scrivendo (scheletri di) codice nella tua testa, quindi scrivendo test per quel codice. Questo non è TDD.

Lasciar andare quell'abitudine è difficile . Davvero molto difficile. Sembra essere particolarmente difficile per i programmatori esperti.

Keith Braithwaite ha creato un esercizio che chiama TDD come se lo intendessi . Consiste in un insieme di regole (basate sulle Tre regole del TDD di zio Bob Martin , ma molto più rigorose) che devi seguire rigorosamente e che sono progettate per orientarti verso l'applicazione del TDD in modo più rigoroso. Funziona meglio con la programmazione di coppia (in modo che la tua coppia possa assicurarsi di non infrangere le regole) e un istruttore.

Le regole sono:

  1. Scrivi esattamente un nuovo test, il test più piccolo possibile che sembra puntare nella direzione di una soluzione
  2. Vederlo fallire; gli errori di compilazione vengono considerati errori
  3. Fai passare il test da (1) scrivendo il codice di implementazione minimo che puoi nel metodo di test .
  4. Rifattore per rimuovere la duplicazione, e altrimenti come richiesto per migliorare il design. Sii severo nell'usare queste mosse:
    1. vuoi un nuovo metodo — attendi il tempo di refactoring, quindi ... crea nuovi metodi (non di prova) eseguendo uno di questi e in nessun altro modo:
      • preferito: eseguire il metodo di estrazione sul codice di implementazione creato come da (3) per creare un nuovo metodo nella classe di test, oppure
      • se è necessario: spostare il codice di implementazione come da (3) in un metodo di implementazione esistente
    2. vuoi una nuova classe: attendi il tempo di refactoring, quindi ... crea classi non di prova per fornire una destinazione per un metodo di spostamento e per nessun altro motivo
    3. popolare le classi di implementazione con i metodi facendo Move Method e nessun altro modo

In genere, questo porterà a progetti molto diversi rispetto al "metodo pseudo-TDD" spesso praticato di "immaginare nella tua testa quale dovrebbe essere il design, quindi scrivere test per forzare quel design, implementare il design che avevi già immaginato prima di scrivere il tuo test".

Quando un gruppo di persone implementa qualcosa di simile a un gioco di tic tac toe usando pseudo-TDD, in genere finiscono con progetti molto simili che coinvolgono un qualche tipo di Boardclasse con un array 3 × 3 di Integers. E almeno una parte dei programmatori avrà effettivamente scritto questa classe senza prove perché "sanno che ne avranno bisogno" o "hanno bisogno di qualcosa per scrivere i loro test". Tuttavia, quando costringi lo stesso gruppo ad applicare il TDD come se lo intendessi, spesso finiranno con una grande varietà di progetti molto diversi, spesso non impiegando nulla di simile a un remoto Board.

C'è un modo per identificare se il test che hai scritto è sufficiente?

Quando coprono tutti i requisiti aziendali. I test sono una codifica dei requisiti di sistema.

È buona norma scrivere test per funzionalità molto semplici che potrebbero essere equivalenti a 1 + 1 = 2 o è solo un overplay?

Ancora una volta, ce l'hai al contrario: non scrivi test per funzionalità. Scrivi funzionalità per i test. Se la funzionalità per superare il test risulta essere banale, va benissimo! Hai appena soddisfatto un requisito di sistema e non hai nemmeno dovuto lavorare sodo per questo!

È utile modificare la funzionalità e testare di conseguenza se i requisiti cambiano?

No. Al contrario. Se un requisito cambia, si modifica il test che corrisponde a quel requisito, lo si osserva fallire, quindi si cambia il codice per farlo passare. I test vengono sempre per primi.

È difficile farlo. Hai bisogno di dozzine, forse centinaia di ore di pratica deliberata per costruire una sorta di "memoria muscolare" per arrivare a un punto, in cui quando la scadenza incombe e sei sotto pressione, non devi nemmeno pensarci e farlo diventa il modo più veloce e naturale per lavorare.


1
Una risposta davvero chiara! Dal punto di vista pratico, un quadro di prova flessibile e potente è molto divertente quando si pratica il TDD. Sebbene indipendente da TDD, la possibilità di eseguire automaticamente test è preziosa per il debug di un'applicazione. Per iniziare con TDD, i programmi non interartistici (stile UNIX) sono probabilmente i più semplici, perché un caso d'uso può essere testato confrontando lo stato di uscita e l'output del programma con ciò che è previsto. Un esempio concreto di questo approccio può essere trovato nella mia biblioteca di benzina per OCaml.
Michael Le Barbier Grünewald,

4
Dici "quando costringi lo stesso gruppo ad applicare il TDD come se lo intendessi, spesso finiranno con una grande varietà di disegni molto diversi, spesso non impiegando nulla di simile a una tavola da remoto" come se fosse una buona cosa . Per me non è affatto chiaro che sia una buona cosa, e potrebbe anche essere brutta dal punto di vista della manutenzione poiché sembra che l'implementazione sarebbe molto controintuitiva per qualcuno di nuovo. Potresti spiegare perché questa diversità di implementazione è positiva o almeno non negativa?
Jim Clay,

3
+1 La risposta è buona in quanto descrive correttamente TDD. Tuttavia, mostra anche perché TDD è una metodologia imperfetta: sono necessari un pensiero attento e una progettazione esplicita, soprattutto di fronte a problemi algoritmici. Fare TDD "alla cieca" (come prescrive TDD) fingendo di non avere alcuna conoscenza del dominio porta a inutili difficoltà e vicoli ciechi. Guarda la famigerata debacle del risolutore di Sudoku (versione corta: TDD non può battere la conoscenza del dominio).
Andres F.

1
@AndresF .: In realtà, il post sul blog a cui ti sei collegato sembra fare eco alle esperienze che Keith ha fatto facendo TDD come se lo intendessi: quando fanno "pseudo-TDD" per Tic-Tac-Toe, iniziano creando una Boardclasse con un 3x3 array di ints (o qualcosa del genere). Considerando che, se li costringi a fare TDDAIYMI, spesso finiscono per creare un mini-DSL per acquisire la conoscenza del dominio. Questo è solo aneddotico, ovviamente. Uno studio statisticamente e scientificamente valido sarebbe carino, ma come spesso accade con studi come questo, sono troppo piccoli o troppo costosi.
Jörg W Mittag,

@ JörgWMittag Correggimi se ti ho frainteso, ma stai dicendo che Ron Jeffries stava facendo "pseudo-TDD"? Non è forse una forma dell'errore "no true Scotsman"? (Concordo con te sulla necessità di ulteriori studi scientifici; il blog a cui ho collegato è solo un colorato aneddoto sullo spettacolare fallimento di un'istanza specifica dell'uso del TDD. Sfortunatamente, sembra che gli evangelisti del TDD siano troppo rumorosi per il resto di noi per avere una vera analisi di questa metolodia e dei suoi presunti benefici).
Andres F.

5

Descrivi il tuo approccio di sviluppo come un processo "top-down-only" - parti da un livello di astrazione più elevato e vai sempre più nei dettagli. TDD, almeno nella forma in cui è popolare, è una tecnica "dal basso verso l'alto". E per qualcuno che lavora principalmente "dall'alto verso il basso" può essere davvero insolito lavorare "dal basso verso l'alto".

Quindi, come puoi aggiungere più "TDD" al tuo processo di sviluppo? In primo luogo, presumo che il tuo processo di sviluppo non sia sempre così "dall'alto verso il basso" come descritto sopra. Dopo il passaggio 2, probabilmente avrai identificato alcuni componenti che sono indipendenti da altri componenti. A volte decidi di implementare prima quei componenti. I dettagli dell'API pubblica di tali componenti probabilmente non seguono i tuoi requisiti da soli, i dettagli seguono anche le tue decisioni di progettazione. Questo è il punto in cui puoi iniziare con TDD: immagina come utilizzerai il componente e come utilizzerai effettivamente l'API. E quando inizi a codificare tale utilizzo dell'API sotto forma di test, hai appena iniziato con TDD.

In secondo luogo, è possibile eseguire TDD anche quando si intende codificare più "top-down", iniziando con componenti che dipendono prima da altri componenti inesistenti. Quello che devi imparare è come "deridere" prima queste altre dipendenze. Ciò ti consentirà di creare e testare componenti di alto livello prima di passare ai componenti di livello inferiore. Un esempio molto dettagliato su come fare TDD in modo discendente si trova in questo post sul blog di Ralf Westphal .


3

Lo sto facendo è giusto? Altrimenti cosa devo cambiare esattamente

Stai andando bene.

C'è un modo per identificare se il test che hai scritto è sufficiente?

Sì, utilizzare uno strumento di copertura test / codice . Martin Fowler offre alcuni buoni consigli sulla copertura dei test.

È buona norma scrivere test per funzionalità molto semplici che potrebbero essere equivalenti a 1 + 1 = 2 o è solo un overplay?

In generale, qualsiasi funzione, metodo, componente, ecc. Che si prevede di produrre un risultato dato alcuni input è un buon candidato per un test unitario. Tuttavia, come per la maggior parte delle cose nella vita (ingegneristica), è necessario considerare i compromessi: lo sforzo è compensato scrivendo il test unitario che porta a una base di codice più stabile nel lungo periodo? In generale, scegliere di scrivere prima il codice di prova per la funzionalità cruciale / critica. In seguito, se si rilevano errori associati a una parte non testata del codice, aggiungere altri test.

È utile modificare la funzionalità e testare di conseguenza se i requisiti cambiano?

La cosa buona di avere test automatici è che vedrai immediatamente se una modifica rompe le asserzioni precedenti. Se ti aspetti questo a causa di requisiti modificati, sì, è ok per cambiare il codice di test (in effetti, in TDD puro cambierai prima i test in base ai requisiti, quindi adotti il ​​codice fino a quando non soddisfa i nuovi requisiti).


La copertura del codice potrebbe non essere una misura molto affidabile. L'applicazione della% di copertura di solito comporta molti test non necessari (come test per tutti i parametri controlli null, ecc. - che sono test per motivi di test che non aggiungono quasi alcun valore) e sprecano tempo di sviluppo, mentre è difficile testare il codice i percorsi potrebbero non essere testati affatto.
Paolo,

3

Scrivere prima i test è un approccio completamente diverso alla scrittura del software. I test non sono solo uno strumento per la corretta verifica della funzionalità del codice (tutti superano) ma la forza che definisce il progetto. Sebbene la copertura del test possa essere una metrica utile, non deve essere l'obiettivo in sé - l'obiettivo del TDD non è quello di raggiungere una buona percentuale di copertura del codice, ma di pensare alla testabilità del codice prima di scriverlo.

Se hai problemi a scrivere prima i test, ti consiglio vivamente di fare una sessione di programmazione in coppia con qualcuno che ha esperienza nel TDD, in modo da avere un'esperienza diretta del "modo di pensare" dell'intero approccio.

Un'altra cosa buona da fare è guardare video online in cui il software viene sviluppato utilizzando TDD sin dalla prima riga di esso. Quello che una volta ero solito presentare al TDD era Let's Play TDD di James Shore. Dai un'occhiata, illustrerà come funziona il design emergente, quali domande dovresti porti mentre scrivi i test e come vengono create, rifattorizzate e ripetute nuove classi e metodi.

C'è un modo per identificare se il test che hai scritto è sufficiente?

Credo che questa sia la domanda sbagliata da porre. Quando fai TDD, hai scelto di fare TDD e design emergente come il modo di scrivere software. Se qualsiasi nuova funzionalità che devi aggiungere inizia sempre con un test, sarà sempre lì.

È buona norma scrivere test per funzionalità molto semplici che potrebbero essere equivalenti a 1 + 1 = 2 o è solo un overplay?

Ovviamente dipende, usa il tuo giudizio. Preferisco non scrivere test sui parametri null check, se il metodo non fa parte dell'API pubblica, ma altrimenti, perché non dovresti confermare che il metodo Add (a, b) restituisce effettivamente a + b?

È utile modificare la funzionalità e testare di conseguenza se i requisiti cambiano?

Ancora una volta, quando modifichi o aggiungi nuove funzionalità al tuo codice, inizi con un test, che si tratti di aggiungere un nuovo test o cambiare quello esistente quando cambiano i requisiti.

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.