TDD vs. produttività


131

Nel mio progetto attuale (un gioco, in C ++), ho deciso di utilizzare Test Driven Development al 100% durante lo sviluppo.

In termini di qualità del codice, questo è stato fantastico. Il mio codice non è mai stato così ben progettato o così privo di bug. Non mi arrabbio quando visualizzo il codice che ho scritto un anno fa all'inizio del progetto, e ho acquisito un senso molto migliore su come strutturare le cose, non solo per essere più facilmente testabile, ma per essere più semplice da implementare e utilizzare .

Tuttavia ... è passato un anno da quando ho iniziato il progetto. Certo, posso lavorarci solo nel mio tempo libero, ma TDD mi sta ancora rallentando considerevolmente rispetto a quello a cui sono abituato. Ho letto che la velocità di sviluppo più lenta migliora nel tempo, e sicuramente penso ai test molto più facilmente rispetto al passato, ma ci sono stato per un anno e sto ancora lavorando al ritmo di una lumaca.

Ogni volta che penso al prossimo passo che necessita di lavoro, devo fermarmi ogni volta e pensare a come scriverei un test per farlo, per permettermi di scrivere il codice reale. A volte rimango bloccato per ore, sapendo esattamente quale codice voglio scrivere, ma non sapendo come scomporlo abbastanza finemente da coprirlo completamente con i test. Altre volte, elaborerò rapidamente una dozzina di test e passerò un'ora a scrivere test per coprire un minuscolo pezzo di codice reale che altrimenti richiederebbe qualche minuto per scrivere.

Oppure, dopo aver terminato il 50 ° test per coprire una particolare entità nel gioco e tutti gli aspetti della sua creazione e utilizzo, guardo la mia lista di cose da fare e vedo la prossima entità da codificare, e rabbrividisco con orrore al pensiero di scrivere altri 50 test simili per implementarlo.

È arrivato al punto che, osservando i progressi dell'ultimo anno, sto pensando di abbandonare TDD per "finire il dannato progetto". Tuttavia, rinunciare alla qualità del codice che ne deriva non è qualcosa che non vedo l'ora. Temo che, se smetterò di scrivere test, abbandonerò l'abitudine di rendere il codice così modulare e testabile.

Sto forse facendo qualcosa di sbagliato per essere ancora così lento in questo? Esistono alternative che accelerano la produttività senza perdere completamente i benefici? TAD? Meno copertura del test? In che modo le altre persone sopravvivono al TDD senza uccidere tutta la produttività e la motivazione?


@Nairou: puoi sempre provare a "finire il progetto"! Crea un ramo ora. Basta scrivere il codice lì dentro. Ma limita ciò che fai, in base al tempo o al numero di entità di gioco e vedi se sei andato più veloce. È quindi possibile ignorare quel ramo, tornare al trunk e TDD da lì e vedere qual è la differenza.
Quamrana,

9
Per me, scrivere i test troppo presto è come ottimizzare troppo presto. Potresti lavorare sodo sul test del codice che rimuoverai in futuro comunque.
LennyProgrammers,

Sono un po 'preoccupato che passi ore a pensare a un modo per progettare il tuo codice in modo che sia più testabile. La testabilità è probabilmente un attributo di un design ben ponderato, ma non dovrebbe essere l'obiettivo principale del design .
Jeremy,

2
Quando stavo imparando, avevamo un trucco per quando dovevamo fornire documenti di progettazione. Prima abbiamo scritto il codice, quindi i documenti per descriverlo. Forse devi imparare una quantità moderata di quel pragmatismo per il tuo TDD. Se hai già in mente un piano, forse è meglio inserirne la maggior parte nel codice prima di scrivere i test. Qualunque cosa suggerisca l'idealismo, a volte è meglio fare ciò che sei già pronto a fare, piuttosto che distrarti con qualcos'altro e poi tornare quando non sei più fresco.
Steve314,

4
Sto andando contro l'opinione popolare e dire che TDD potrebbe non essere sempre la scelta giusta se stai facendo giochi. Dato che qualcuno su gamedev.stackexchange ha già risposto a questa domanda in modo spettacolare, collegherò qui .
Bangkok,

Risposte:


77

Vorrei iniziare ringraziandoti per condividere la tua esperienza e esprimere le tue preoccupazioni ... che devo dire non sono rare.

  • Tempo / Produttività: scrivere test è più lento di non scrivere test. Se lo cerchi, sarei d'accordo. Tuttavia, se si esegue uno sforzo parallelo in cui si applica un approccio non TDD, è probabile che il tempo che impieghi nel codice esistente break-detect-debug-and-fix ti ponga in netto negativo. Per me, TDD è il più veloce che posso fare senza compromettere la mia sicurezza del codice. Se trovi elementi nel tuo metodo che non aggiungono valore, eliminali.
  • Numero di test: se si codificano N cose, è necessario testare N cose. per parafrasare una delle righe di Kent Beck " Prova solo se vuoi che funzioni " .
  • Rimanere bloccato per ore: lo faccio anche io (a volte e non> 20 minuti prima di interrompere la linea). È solo il tuo codice che ti dice che il design ha bisogno di un po 'di lavoro. Un test è solo un altro client per la tua classe SUT. Se un test trova difficoltà nell'utilizzare il tuo tipo, è probabile che lo siano anche i tuoi clienti di produzione.
  • Test analoghi tedio: questo ha bisogno di un po 'più di contesto per poter scrivere un contro-argomento. Detto questo, fermati e pensa alla somiglianza. Riesci a guidare in qualche modo quei test? È possibile scrivere test su un tipo di base? Quindi è sufficiente eseguire lo stesso set di test per ogni derivazione. Ascolta i tuoi test. Sii il giusto tipo di pigro e vedi se riesci a trovare un modo per evitare la noia.
  • Smettere di pensare a cosa devi fare dopo (il test / le specifiche) non è una brutta cosa. Al contrario, è raccomandato per costruire "la cosa giusta". Di solito se non riesco a pensare a come testarlo, di solito non riesco nemmeno a pensare all'implementazione. È una buona idea sopprimere le idee di implementazione fino ad arrivare lì .. forse una soluzione più semplice è oscurata da un design preventivo YAGNI.

E questo mi porta alla domanda finale: come posso migliorare? La mia (o An) risposta è Leggi, Rifletti ed Esercita.

ad es. di recente, tengo d'occhio

  • se il mio ritmo riflette RG [Ref] RG [Ref] RG [Ref] o è RRRRGRRef.
  • % di tempo trascorso nello stato Errore di compilazione / rosso.
  • Sono bloccato in uno stato di build Red / Broken?

1
Sono molto curioso del tuo commento sulla guida dei dati nei test. Intendi solo un singolo set di test che elabora dati esterni (come da un file) anziché ripetere il test di un codice simile? Nel mio gioco ho più entità e ognuna è in gran parte diversa, ma ci sono alcune cose comuni che vengono fatte con esse (serializzandole sulla rete, assicurandomi che non vengano inviate a giocatori inesistenti, ecc.) Finora non ho trovato un modo per consolidare questo, e così ho una serie di test per ognuno che sono quasi identici, diversi solo in quale entità usano e quali dati contiene.

@Nairoi - Non sono sicuro del test runner che stai utilizzando. Ho appena imparato un nome per quello che volevo trasmettere. Schema di fissaggio astratto [ goo.gl/dWp3k] . Ciò richiede comunque di scrivere quante Fixture quanti sono i tipi SUT concreti. Se vuoi essere ancora più conciso, guarda i documenti del tuo corridore. ad esempio NUnit supporta dispositivi di prova parametrici e generici (ora che l'ho cercato) goo.gl/c1eEQ Sembra proprio ciò di cui hai bisogno.
Gishu,

Interessante, non ho mai sentito parlare di apparecchi astratti. Attualmente uso UnitTest ++ che ha dispositivi, ma non quelli astratti. I suoi infissi sono molto letterali, solo un modo per consolidare il codice di test che altrimenti si ripeterebbe in ogni test, per un dato gruppo di test.

@asgeo - impossibile modificare quel commento .. il link ha preso una parentesi finale. Dovrebbe funzionare - goo.gl/dWp3k
Gishu

+1 per "rimanere bloccati è il sintomo del progetto ha bisogno di più lavoro", anche se ... cosa succede quando rimani bloccato (come me) al design?
Lurscher,

32

Non hai bisogno del 100% di copertura del test. Sii pragmatico.


2
Se non hai una copertura del test al 100%, non hai la sicurezza del 100%.
Christopher Mahan,

60
Non hai il 100% di confidenza anche con il 100% di copertura del test. Questo è il test 101. I test non possono dimostrare che il codice sia privo di difetti; al contrario, possono solo dimostrare che contiene difetti.
CesarGon,

7
Per quello che vale, uno dei sostenitori più appassionati di TDD, Bob Martin, non raccomanda una copertura del 100% - blog.objectmentor.com/articles/2009/01/31/… . Nel settore manifatturiero (garantito, diverso dal software per molti aspetti), nessuno si fida del 100% di fiducia perché può spendere una frazione dello sforzo per essere fiducioso al 99%.
Probabilità

Inoltre (almeno l'ultima volta che ho controllato gli strumenti di cui disponiamo) i rapporti sulla copertura del codice si riferiscono al fatto che le linee siano state eseguite ma non includono la copertura del valore - ad esempio oggi ho avuto un bug segnalato in cui avevo eseguito tutti i percorsi attraverso il codice nei test, ma poiché c'era una linea simile a = x + ye sebbene tutte le righe nel codice fossero eseguite nei test, i test sono stati testati solo nel caso in cui y = 0, quindi il bug (avrebbe dovuto essere a = x - y) non è mai stato trovato nei test.
Pete Kirkham,

@Chance - Ho letto il libro di Robert Martin "Clean coder ..." un nome lungo. In quel libro si diceva che avrebbe dovuto essere asintoticamente coperto al 100% dai test, che è vicino al 100%. E il link al blog non funziona più.
Darius.V

22

TDD mi sta ancora rallentando considerevolmente

Questo è in realtà falso.

Senza TDD, trascorri alcune settimane a scrivere codice che funziona per lo più e trascorri l'anno successivo a "testare" e correggere molti (ma non tutti) i bug.

Con TDD, passi un anno a scrivere codice che funziona davvero. Quindi esegui il test di integrazione finale per alcune settimane.

Il tempo trascorso sarà probabilmente lo stesso. Il software TDD avrà una qualità sostanzialmente migliore.


6
Quindi perché ho bisogno del TDD? "Il tempo trascorso è lo stesso"

21
@Peter Long: qualità del codice. L'anno "testing" è il modo in cui finiamo con il software di merda che funziona principalmente.
S. Lott,

1
@Peter, stai scherzando. La qualità della soluzione TDD sarà di gran lunga superiore.
Mark Thomas,

7
Perché ho bisogno del TDD? Kent Beck indica che la tranquillità è grande, ed è molto convincente per me. Vivo nella costante paura di rompere le cose quando lavoro su un codice senza test unitari.

7
@Peter Long: "Il tempo trascorso è lo stesso" ... e in qualsiasi momento durante quel periodo puoi consegnare il codice di lavoro .
Frank Shearar,

20

Oppure, dopo aver terminato il 50 ° test per coprire una particolare entità nel gioco e tutti gli aspetti della sua creazione e utilizzo, guardo la mia lista di cose da fare e vedo la prossima entità da codificare, e rabbrividisco con orrore al pensiero di scrivere altri 50 test simili per implementarlo.

Questo mi fa chiedere quanto stai seguendo il passo "Refactor" di TDD.

Quando tutti i test vengono superati, è tempo di refactoring del codice e rimuovere la duplicazione. Mentre le persone di solito lo ricordano, a volte dimenticano che anche questo è il momento di riformattare i loro test , rimuovere la duplicazione e semplificare le cose.

Se hai due entità che si uniscono in una per consentire il riutilizzo del codice, prendi in considerazione anche l'unione dei loro test. Devi solo testare differenze incrementali nel tuo codice. Se non esegui regolarmente la manutenzione sui tuoi test, questi possono diventare rapidamente ingombranti.

Un paio di punti filosofici sul TDD che potrebbero essere utili:

  • Quando non riesci a capire come scrivere un test, nonostante la lunga esperienza nella scrittura di test, è sicuramente un odore di codice . Il tuo codice è in qualche modo privo di modularità, il che rende difficile scrivere piccoli e semplici test.
  • L'aggiunta di un po 'di codice è perfettamente accettabile quando si utilizza TDD. Scrivi il codice che desideri, per avere un'idea di come appare, quindi elimina il codice e inizia con i test.
  • Vedo la pratica del TDD estremamente rigoroso come una forma di esercizio. Quando inizi, scrivi sicuramente un test prima ogni volta e scrivi il codice più semplice per far passare il test prima di proseguire. Tuttavia, questo non è necessario quando si diventa più a proprio agio con la pratica. Non ho un unit test per ogni singolo possibile percorso di codice che scrivo, ma per esperienza sono in grado di scegliere cosa deve essere testato con un unit test e cosa può essere coperto invece da test funzionali o di integrazione. Se pratichi TDD in modo rigoroso da un anno, immagino che anche tu sei vicino a questo punto.

EDIT: Sul tema della filosofia dei test unitari, penso che potrebbe essere interessante per te leggere: The Way of Testivus

E un punto più pratico, se non necessariamente molto utile, di:

  • Citi C ++ come linguaggio di sviluppo. Ho praticato ampiamente TDD in Java, usando eccellenti librerie come JUnit e Mockito. Tuttavia, ho trovato TDD in C ++ molto frustrante a causa della mancanza di librerie (in particolare, framework di derisione) disponibili. Sebbene questo punto non ti aiuti molto nella tua situazione attuale, spero che tu lo prenda in considerazione prima di abbandonare del tutto il TDD.

4
I test di refactoring sono pericolosi. Nessuno sembra parlarne, ma lo è. Certamente non ho test unitari per testare i test unitari. Quando si esegue il refactoring per ridurre la duplicazione, in genere si aumenta la complessità (perché il codice diventa più generale). Ciò significa che è più probabile che ci sia un bug nei tuoi test .
Scott Whitlock,

2
Non sono d'accordo sul fatto che i test di refactoring siano pericolosi. Stai effettuando il refactoring solo quando tutto passa, quindi se esegui un refactoring e tutto è ancora verde, allora stai bene. Se stai pensando di dover scrivere test per i tuoi test, penso che sia un indicatore che devi scrivere test più semplici.
Jaustin,

1
Il C ++ è difficile da testare l'unità (il linguaggio non fa facilmente cose che rendono facili le beffe). Ho notato che le funzioni che sono "funzioni" (funzionano solo su argomenti, i risultati escono come valori di ritorno / parametri) sono molto più facili da testare rispetto alle "procedure" (return void, no args). Ho scoperto che in realtà può essere più semplice testare il codice C modulare ben realizzato rispetto al codice C ++. Non devi scrivere in C, ma puoi seguire l'esempio C modulare. Sembra del tutto folle, ma ho messo i test unitari sulla "cattiva C", dove tutto era globale ed era super facile, tutto lo stato è sempre disponibile !.
anon

2
Penso che questo sia molto vero. Faccio molto RedGreenRedGreenRedGreen (o, più spesso, RedRedRedGreenGreenGreen), ma raramente refactoring. I miei test di certo non sono mai stati sottoposti a refactoring, poiché ho sempre pensato che perderebbe ancora più tempo a non programmare. Ma posso vedere come potrebbe essere la causa dei problemi che sto affrontando. È tempo per me di esaminare seriamente il refactoring e il consolidamento.
Nairou,

1
Framework di derisione di Google C ++ (integrato con Google C ++ test fw) - libreria di derisione molto, molto potente - flessibile, ricco di funzionalità - abbastanza comparabile con qualsiasi altro framework di derisione in circolazione.
Ratkok,

9

Domanda molto interessante.

La cosa importante da notare è che il C ++ non è facilmente testabile e, in generale, il gioco è anche un pessimo candidato per TDD. Non è possibile verificare se OpenGL / DirectX disegna triangolo rosso con driver X e giallo con driver Y facilmente. Se il vettore normale della bump map non viene capovolto dopo la trasformazione dello shader. Né è possibile testare i problemi di ritaglio sulle versioni dei driver con precisioni diverse e così via. Il comportamento di disegno indefinito a causa di chiamate errate può anche essere testato solo con un'accurata revisione del codice e SDK a portata di mano. Anche il suono è un cattivo candidato. Il multithreading, che è di nuovo molto importante per i giochi, è praticamente inutile per i test unitari. Quindi è dura.

Fondamentalmente il gioco è un sacco di interfaccia grafica, suono e thread. La GUI, anche con componenti standard a cui è possibile inviare WM_, è difficile da testare.

Quindi ciò che puoi testare sono le classi di caricamento dei modelli, le classi di caricamento delle trame, le librerie di matrici e alcune di queste, che non sono un sacco di codice e, abbastanza spesso, non molto riutilizzabili, se è solo il tuo primo progetto. Inoltre, sono confezionati in formati proprietari, quindi non è molto probabile che l'input di terze parti possa differire molto, a meno che non rilasci strumenti di modding ecc.

Inoltre, non sono guru o evangelista TDD, quindi prendi tutto questo con un granello di sale.

Probabilmente scriverei alcuni test per i componenti principali (ad esempio la libreria di matrici, la libreria di immagini). Aggiungi un sacco di abort()input imprevisti in ogni funzione. E, soprattutto, concentrati sul codice resistente / resiliente che non si rompe facilmente.

Per quanto riguarda gli errori, l'uso intelligente di C ++, RAII e un buon design fanno molto per prevenirli.

Fondamentalmente hai molto da fare solo per coprire le basi se vuoi rilasciare il gioco. Non sono sicuro se TDD aiuterà.


3
+1 Mi piace molto il concetto di TDD e lo uso dove posso, ma tu sollevi un punto molto importante su cui i sostenitori del TDD sono curiosamente silenziosi. Esistono molti tipi di programmazione come hai sottolineato per i quali scrivere test unitari significativi è estremamente difficile se non impossibile. Usa TDD dove ha senso, ma alcuni tipi di codice sono meglio sviluppati e testati in altri modi.
Mark Heath,

@Mark: sì, nessuno sembra preoccuparsi dei test di integrazione al giorno d'oggi, pensando di avere una suite di test automatizzata, tutto funzionerà magicamente quando messo insieme e provato con dati reali.
gbjbaanb,

D'accordo con questo Grazie per una risposta pragmatica che non prescrive dogmaticamente il TDD come risposta a tutto, invece di quello che è, che è solo un altro strumento nel toolkit per sviluppatori.
jb

6

Sono d'accordo con le altre risposte, ma voglio anche aggiungere un punto molto importante: i costi di refactoring !!

Con test unitari ben scritti, puoi tranquillamente riscrivere il tuo codice. Innanzitutto, test unitari ben scritti forniscono un'eccellente documentazione dell'intento del codice. In secondo luogo, eventuali effetti collaterali sfavorevoli del refactoring verranno rilevati nella suite di test esistente. Pertanto, hai garantito che le ipotesi del tuo vecchio codice sono vere anche per il tuo nuovo codice.


4

In che modo le altre persone sopravvivono al TDD senza uccidere tutta la produttività e la motivazione?

Questo è completamente diverso dalle mie esperienze. O sei incredibilmente intelligente e scrivi codice senza bug, (ad es. Con un errore) o non ti rendi conto che il tuo codice ha dei bug che impediscono il funzionamento del tuo programma, e quindi non sono effettivamente finiti.

TDD riguarda l'umiltà di sapere che tu (e io!) Commettiamo errori.

Per me il tempo di scrivere unittest è più che risparmiato in tempi di debug ridotti per i progetti che vengono eseguiti utilizzando TDD dall'inizio.

Se non commetti errori, forse TDD non è così importante per te come lo è per me!


Bene, hai anche dei bug nel tuo codice TDD;)
Coder,

Che il vero! ma tendono ad essere un diverso tipo di bug, se TDD viene eseguito correttamente. Immagino che il codice debba essere privo di bug al 100% per essere completato non è giusto. Sebbene se si definisce un bug come una deviazione dal comportamento definito dal test unitario, allora suppongo che sia privo di bug :)
Tom

3

Ho solo alcune osservazioni:

  1. Sembra che tu stia provando a testare tutto . Probabilmente non dovresti, solo i casi ad alto rischio e limite di un particolare pezzo di codice / metodo. Sono abbastanza sicuro che la regola 80/20 si applica qui: spendi l'80% scrivendo test per l'ultimo 20% del tuo codice o casi che non sono coperti.

  2. Dare priorità. Entra nello sviluppo di software agile e fai un elenco di ciò che devi veramente fare per rilasciarlo in un mese. Quindi rilasciare, proprio così. Questo ti farà pensare alla priorità delle funzionalità. Sì, sarebbe bello se il tuo personaggio potesse fare un backflip, ma ha valore commerciale ?

TDD è buono, ma solo se non miri alla copertura del test al 100%, e non lo è se ti impedisce di produrre valore commerciale effettivo (ovvero funzionalità, elementi che aggiungono qualcosa al tuo gioco).


1

Sì, la scrittura di test e codice potrebbe richiedere più tempo rispetto alla semplice scrittura di codice, ma la scrittura di codice e test unitari associati (utilizzando TDD) è molto più prevedibile della scrittura di codice e quindi del debug.

Il debug viene quasi eliminato quando si utilizza TDD, il che rende tutti i processi di sviluppo molto più prevedibili e, alla fine, probabilmente più veloci.

Refactoring costante: è impossibile eseguire qualsiasi refactoring serio senza una suite completa di test unitari. Il modo più efficiente per costruire quella rete di sicurezza basata su test unitari è durante TDD. Il codice ben refactored migliora significativamente la produttività complessiva del progettista / team che mantiene il codice.


0

Prendi in considerazione la possibilità di restringere l'ambito del tuo gioco e portalo dove qualcuno può giocarlo o lo rilasci. Mantenere i tuoi standard di prova senza dover aspettare troppo tempo per rilasciare il tuo gioco potrebbe essere una via di mezzo per mantenerti motivato. Il feedback dei tuoi utenti può offrire vantaggi a lungo termine e i tuoi test ti consentono di sentirti a tuo agio con le aggiunte e le modifiche.

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.