Come fanno le persone che fanno TDD a gestire la perdita di lavoro quando si eseguono importanti refactoring

37

Da un po 'di tempo ho cercato di imparare a scrivere test unitari per il mio codice.

Inizialmente ho iniziato a fare il vero TDD, in cui non avrei scritto alcun codice fino a quando non avessi scritto per prima cosa un test negativo.

Tuttavia, di recente ho avuto un problema spinoso da risolvere che ha coinvolto un sacco di codice. Dopo aver trascorso un paio di settimane a scrivere test e poi codice, sono giunto alla sfortunata conclusione che il mio intero approccio non avrebbe funzionato, e avrei dovuto buttare via due settimane di lavoro e ricominciare.

Questa è una decisione abbastanza brutta da venire quando hai appena scritto il codice, ma quando hai scritto anche diverse centinaia di test unitari diventa ancora più difficile dal punto di vista emotivo buttare tutto via.

Non posso fare a meno di pensare che ho sprecato 3 o 4 giorni di sforzi scrivendo quei test quando avrei potuto mettere insieme il codice per la dimostrazione del concetto e poi aver scritto i test dopo che ero soddisfatto del mio approccio.

In che modo le persone che praticano il TDD gestiscono correttamente tali situazioni? C'è un motivo per piegare le regole in alcuni casi o scrivi sempre pedissequamente i test, anche quando quel codice potrebbe rivelarsi inutile?

    
posta GazTheDestroyer 09.02.2012 - 10:18
fonte

11 risposte

33

Sento che ci sono due problemi qui. Il primo è che non ti sei reso conto in anticipo che il tuo design originale potrebbe non essere l'approccio migliore. Se l'avessi saputo in anticipo, potresti aver scelto di sviluppare un breve prototipo di lancio o due, per esplorare le possibili opzioni di progettazione e valutare quale sia il modo più promettente da seguire. Nella prototipazione, non è necessario scrivere codice di qualità di produzione e non è necessario testare l'unità ogni angolo (o quasi), poiché l'unico obiettivo è l'apprendimento, non la lucidatura del codice.

Ora, rendersi conto che hai bisogno di prototipi e esperimenti piuttosto che avviare subito lo sviluppo del codice di produzione, non è sempre facile, e nemmeno mai possibile. Armati delle conoscenze appena acquisite, potresti essere in grado di riconoscere la necessità della prototipazione la prossima volta. O forse no. Ma almeno ora sai che questa opzione è da considerare. E questo di per sé è una conoscenza importante.

L'altro problema è IMHO con la tua percezione. Tutti commettiamo errori, ed è così facile vedere a posteriori che cosa avremmo dovuto fare diversamente. Questo è solo il modo in cui apprendiamo. Annota il tuo investimento in unit test come il prezzo di apprendere che la prototipazione può essere importante e superarla. Basta sforzarci di non fare lo stesso errore due volte: -)

    
risposta data 09.02.2012 - 11:33
fonte
8

Il punto di TDD è che ti costringe a scrivere piccoli incrementi di codice in piccole funzioni , precisamente per evitare questo problema. Se hai trascorso settimane a scrivere il codice su un dominio, e ogni singolo metodo di utilità che hai scritto diventa inutile quando ripensi l'architettura, allora i tuoi metodi sono quasi certamente troppo grandi in primo luogo. (Sì, sono consapevole che questo non è esattamente confortante ora ...)

    
risposta data 09.02.2012 - 10:31
fonte
6

Brooks ha dichiarato "il piano per buttarne uno, lo farai comunque". Mi sembra che tu stia facendo proprio questo. Detto questo, dovresti scrivere i tuoi test unitari per testare l'unità di codice e non un gran numero di codice. Questi sono test più funzionali e quindi dovrebbero superare qualsiasi implementazione interna.

Ad esempio, se voglio scrivere un risolutore PDE (equazioni differenziali parziali), scriverò alcuni test cercando di risolvere le cose che posso risolvere matematicamente. Questi sono i miei primi test "unitari" - leggi: test funzionali eseguiti come parte di un framework xUnit. Quelli non cambieranno a seconda dell'algoritmo che uso per risolvere la PDE. Tutto quello che mi interessa è il risultato. I test della seconda unità si concentreranno sulle funzioni utilizzate per codificare l'algoritmo e quindi sarebbero specifici dell'algoritmo, ad esempio Runge-Kutta. Se dovessi scoprire che Runge-Kutta non era adatto, allora avrei ancora quei test di livello superiore (compresi quelli che hanno dimostrato che Runge-Kutta non era adatto). Quindi la seconda iterazione avrebbe ancora molti degli stessi test del primo.

Il tuo problema potrebbe essere sul design e non necessariamente sul codice. Ma senza ulteriori dettagli, è difficile da dire.

    
risposta data 09.02.2012 - 11:22
fonte
5

Devi tenere a mente che TDD è un processo iterativo. Scrivi un piccolo test (nella maggior parte dei casi alcune righe dovrebbero essere sufficienti) ed eseguilo. Il test dovrebbe fallire, ora lavora direttamente sulla tua fonte principale e prova ad implementare la funzionalità testata in modo che il test passi. Ora ricomincia.

Non dovresti provare a scrivere tutti i test in una volta sola, perché, come avrai notato, questo non funzionerà. Ciò riduce il rischio di perdere tempo scrivendo test che non verranno utilizzati.

    
risposta data 09.02.2012 - 10:47
fonte
4

Penso che l'abbia detto tu stesso: non eri sicuro del tuo approccio prima di iniziare a scrivere tutti i tuoi test unitari.

La cosa che ho imparato confrontando i progetti TDD di vita reale con cui ho lavorato (non molti, infatti, solo 3 che coprono 2 anni di lavoro) con quello che avevo appreso teoricamente, è che Automated Testing! = Unit Testing (senza di Ovviamente si escludono a vicenda).

In altre parole, il T in TDD non deve avere una U con esso ... È automatizzato, ma è meno un test unitario (come nel test di classi e metodi) di un test funzionale automatizzato: è a lo stesso livello di granularità funzionale dell'architettura su cui stai attualmente lavorando. Si inizia ad alto livello, con pochi test e solo la grande immagine funzionale, e solo alla fine si finisce con migliaia di UT e tutte le classi ben definite in una bella architettura ...

I test unitari ti danno un grande aiuto quando lavori in team, per evitare modifiche al codice che creano cicli infiniti di bug. Ma non ho mai scritto nulla di così preciso quando ho iniziato a lavorare su un progetto, prima di avere almeno un POC di lavoro globale per ogni story utente.

Forse è solo il mio modo personale di farlo. Non ho l'esperienza sufficiente per decidere da zero quali sono i pattern o la struttura del mio progetto, quindi non perderò il mio tempo scrivendo 100s di UTs dall'inizio ...

Più in generale, l'idea di rompere tutto e lanciare tutto sarà sempre lì. Essendo "continui" come possiamo provare ad essere con i nostri strumenti e metodi, a volte l'unico modo per combattere l'entropia è ricominciare da capo. Ma l'obiettivo è che quando ciò accadrà, i test automatici e unitari che hai implementato avranno reso il tuo progetto già meno costoso di se non ci fosse - e lo sarà, se trovi l'equilibrio.

    
risposta data 09.02.2012 - 13:20
fonte
4
How do people who practice TDD properly handle such situations?
  1. considerando quando prototipizzare vs quando codificare
  2. realizzando che il test dell'unità non è lo stesso di TDD
  3. da scritti TDD verifica per verificare una caratteristica / storia, non un'unità funzionale

La fusione dei test unitari con lo sviluppo basato sui test è fonte di molta angoscia e guai. Quindi rivediamolo ancora:

  • testing dell'unità riguarda la verifica di ogni singolo modulo e funzione nell'implementazione ; in UT vedrai un'enfasi su aspetti come metriche di copertura del codice e test che si eseguono molto rapidamente
  • sviluppo basato su test riguarda la verifica di ogni funzione / storia nei requisiti ; in TDD vedrai l'enfasi su cose come scrivere il test per primo, assicurando che il codice scritto non superi lo scopo previsto e il refactoring per la qualità

In sintesi: il test dell'unità ha un obiettivo di implementazione, TDD ha un focus sui requisiti. Non sono la stessa cosa.

    
risposta data 09.02.2012 - 16:57
fonte
3

Lo sviluppo orientato al test ha lo scopo di guidare il tuo sviluppo. I test che scrivi ti aiutano ad affermare la correttezza del codice che stai attualmente scrivendo e ad aumentare la velocità di sviluppo dalla prima riga in poi.

Sembri credere che i test siano un fardello e, in seguito, solo per lo sviluppo incrementale. Questa linea di pensiero non è in linea con TDD.

Forse è possibile confrontarlo con la tipizzazione statica: sebbene si possa scrivere codice senza alcuna informazione di tipo statico, l'aggiunta di un tipo statico al codice aiuta a far valere certe proprietà del codice, liberando la mente e concentrandosi invece su una struttura importante, aumentando così la velocità e l'efficacia.

    
risposta data 09.02.2012 - 11:34
fonte
2

Il problema con un importante refactoring è che a volte puoi seguire un percorso che ti porta a capire che hai morso più di quanto puoi masticare. I refettori giganti sono un errore. Se il design del sistema è imperfetto, in primo luogo, il refactoring potrebbe solo portarti così lontano prima di dover prendere una decisione difficile. O lascia il sistema così com'è e aggiralo, o pensa di riprogettarlo e apportare alcune modifiche importanti.

C'è comunque un altro modo. Il vero vantaggio del codice di refactoring è rendere le cose più semplici, facili da leggere e persino più facili da mantenere. Dove ti avvicini a un problema di cui hai incertezza, apporti un cambiamento, vai così lontano per vedere dove potrebbe portare al fine di saperne di più sul problema, poi butta via il picco e applica un nuovo refactoring basato su quale picco ti ha insegnato Il fatto è che puoi davvero migliorare il tuo codice con certezza solo se i passaggi sono piccoli e gli sforzi di refactoring non superano la tua capacità di scrivere prima i test. La tentazione è di scrivere un test, quindi il codice, quindi codificarne altri perché una soluzione può sembrare ovvia, ma presto ti rendi conto che il tuo cambiamento cambierà molti altri test, quindi devi stare attento a cambiare solo una cosa alla volta.

La risposta quindi è di non rendere mai più importante il tuo refactoring. Piccoli passi. Inizia estraendo metodi, quindi cerca di rimuovere la duplicazione. Quindi passa all'estrazione di classi. Ciascuno in piccoli passi un piccolo cambiamento alla volta. SE stai estraendo il codice, scrivi prima un test. Se stai rimuovendo il codice, rimuovilo ed esegui i test e decidi se uno qualsiasi dei test non funzionerà più. Un piccolo passo bambino alla volta. Sembra che ci vorrà più tempo, ma in realtà abbrevierà considerevolmente il tuo tempo di refactoring.

La realtà è comunque che ogni picco è apparentemente un potenziale spreco di energie. Le modifiche al codice a volte non vanno da nessuna parte e ti ritrovi a ripristinare il codice dai tuoi vcs. Questa è solo una realtà di ciò che facciamo di giorno in giorno. Ogni spike che fallisce non è sprecato comunque, se ti insegna qualcosa. Ogni sforzo di refactoring che fallisce ti insegnerà che stai cercando di fare troppo troppo velocemente, o che il tuo approccio potrebbe essere sbagliato. Anche quello non è una perdita di tempo se impari qualcosa da esso. Più fai questa roba, più impari e più efficiente diventerai. Il mio consiglio è di indossarlo per ora, imparare a fare di più facendo di meno, e accettare che questo è esattamente il modo in cui probabilmente devono essere le cose fino a quando non sarai più in grado di identificare quanto lontano prendere un picco prima che non ti porti da nessuna parte. / p>     

risposta data 09.02.2012 - 22:49
fonte
1

Non sono sicuro del motivo per cui il tuo approccio è fallito dopo 3 giorni. A seconda delle tue incertezze nella tua architettura, potresti prendere in considerazione la possibilità di cambiare la tua strategia di test:

  • Se non sei sicuro delle prestazioni, potresti iniziare con alcuni test di integrazione che affermano le prestazioni?

  • Quando la complessità dell'API è ciò che stai studiando, scrivi alcuni test di unità reali per scoprire quale sarebbe il modo migliore per farlo. Non preoccuparti di implementare nulla, solo fai in modo che le classi restituiscano valori codificati rigidi o facciali lanciare NotImplementedExceptions.

risposta data 09.02.2012 - 13:33
fonte
0

Per me i test unitari sono anche un'occasione per mettere l'interfaccia sotto "reale" uso (beh, reale come i test unitari!).

Se sono costretto a fare un test devo esercitare il mio design. Questo aiuta a mantenere le cose buone (se qualcosa è così complesso che scrivere un test per questo è un peso, come sarà usarlo?).

Ciò non evita cambiamenti nel design, piuttosto espone la necessità per loro. Sì, una riscrittura completa è un dolore. Per (provare a) evitarlo di solito installo (uno o più) prototipi, possibilmente in Python (con lo sviluppo finale in c ++).

Certo, non hai sempre il tempo per tutte queste chicche. Questi sono precisamente i casi in cui hai bisogno di un GRANDE quantità di tempo per raggiungere i tuoi obiettivi ... e / o per tenere tutto sotto controllo.

    
risposta data 13.02.2012 - 19:40
fonte
0

Benvenuto nel circo degli sviluppatori creativi .


Invece di rispettare il modo "legale / ragionevole" di codificare all'inizio,
 prova intuition , soprattutto se è importante e nuovo per te e se nessun campione in giro sembra che tu voglia:

- Scrivi con il tuo istinto, da cose che già conosci, non con la tua mente e immaginazione.
- E basta.
- Prendi una lente d'ingrandimento e controlla tutte le parole che scrivi: Scrivi "testo" perché "testo" è vicino a String, ma è necessario "verbo", "aggettivo" o qualcosa di più accurato, rileggere e regolare il metodo con un nuovo significato
 ... o hai scritto un pezzo di codice pensando al futuro? rimuovilo - Corretto, svolgere altri compiti (sport, cultura o altre cose al di fuori del mondo degli affari),  torna indietro e leggi di nuovo.
- Tutto si adatta bene, passa a UML
- Correggere, fare altro compito, tornare indietro e leggere di nuovo.
- Tutto va bene, passare a TDD
- Ora tutto è corretto, buono - Prova il punto di riferimento per indicare le cose da ottimizzare, fallo.

Cosa appare:
- hai scritto un codice rispettando tutte le regole
- ottieni un'esperienza, un nuovo modo di lavorare,
- qualcosa cambia nella tua mente, non sarai mai spaventato dalla nuova configurazione.

E ora, se vedi un UML simile a quello sopra, sarai in grado di dire "Boss, inizio da TDD per questo ...."
è un'altra cosa nuova?
"Capo, proverei qualcosa prima di decidere come codificherò .."

Cordiali saluti da PARIGI Claude

    
risposta data 15.02.2012 - 10:33
fonte

Leggi altre domande sui tag