Senso di test unitari senza TDD

28

Abbiamo un nuovo (piuttosto grande) progetto che inizia, che abbiamo pianificato di sviluppare usando TDD.

L'idea di TDD è fallita (molte ragioni commerciali e non commerciali), ma al momento abbiamo una conversazione - dovremmo scrivere comunque test di unità, o meno. Il mio amico dice che non c'è alcun senso (o quasi zero) nello scrivere test di unità senza TDD, dovremmo concentrarci solo sui test di integrazione. Credo al contrario, che ci sia ancora un certo senso nello scrivere semplici test unitari, solo per rendere il codice più a prova di futuro. Cosa ne pensi?

Aggiunto: Penso che questo non sia un duplicato di > > question < < - Capisco la differenza tra UT e TDD. La mia domanda è non sulle differenze , ma sul senso di scrivere Test di unità senza TDD.

    
posta ex3v 08.08.2014 - 14:56
fonte

6 risposte

52

Il TDD è utilizzato principalmente (1) per garantire la copertura, (2) e per guidare un progetto verificabile, comprensibile e verificabile. Se non si utilizza TDD, non si ottiene la copertura del codice garantita. Ma ciò non significa in alcun modo che tu debba abbandonare questo obiettivo e vivere alla perfezione con una copertura dello 0%.

I test di regressione sono stati inventati per una ragione. Il motivo è che, a lungo termine, ti fanno risparmiare più tempo in errori evitati di quanto non facciano in più per scrivere. Questo è stato dimostrato più e più volte. Pertanto, a meno che tu non sia seriamente convinto che la tua organizzazione sia molto, molto più buona dell'ingegneria del software di tutti i guru che raccomandano i test di regressione (o se hai intenzione di scendere molto presto in modo che non non sia a lungo termine per te), sì, dovresti assolutamente avere test unitari, esattamente per la ragione che si applica a praticamente ogni altra organizzazione nel mondo: perché catturano gli errori prima dei test di integrazione, e questo ti farà risparmiare denaro. Non scriverli è come far passare soldi gratis semplicemente per strada.

    
risposta data 08.08.2014 - 15:02
fonte
21

Ho un aneddoto pertinente da qualcosa che sta succedendo adesso per me. Sono su un progetto che non usa TDD. La nostra gente del QA ci sta muovendo in quella direzione, ma siamo un piccolo outfit ed è stato un processo lungo e articolato.

Comunque , di recente stavo usando una libreria di terze parti per svolgere un'attività specifica. C'era un problema riguardante l'uso di quella libreria, quindi è stato messo su di me per scrivere essenzialmente una versione di quella stessa libreria per conto mio. In totale, sono state circa 5.000 linee di codice eseguibile e circa 2 mesi del mio tempo. So che le linee di codice sono una metrica scadente, ma per questa risposta ritengo che sia un indicatore di magnitudine decente.

C'era una particolare struttura di dati di cui avevo bisogno che mi avrebbe permesso di tenere traccia di un numero arbitrario di bit. Dato che il progetto è in Java, ho scelto il BitSet di Java e l'ho modificato un po '(avevo bisogno anche della capacità di tracciare il 0 s principale, che il BitSet di Java non fa per qualche motivo .....) . Dopo aver raggiunto il ~ 93% di copertura, ho iniziato a scrivere alcuni test che avrebbero messo in evidenza il sistema che avevo scritto. Avevo bisogno di confrontare alcuni aspetti della funzionalità per garantire che fossero abbastanza veloci per i miei requisiti finali. Non sorprende che una delle funzioni che avevo sovrascritto dall'interfaccia BitSet fosse assurdamente lenta quando si trattava di insiemi di bit di grandi dimensioni (centinaia di milioni di bit in questo caso). Altre funzioni sovrascritte si basavano su questa funzione, quindi era un enorme collo di bottiglia.

Ciò che ho finito per fare è andare al tavolo da disegno e capire come manipolare la struttura sottostante di BitSet , che è un long[] . Ho progettato l'algoritmo, ho chiesto ai colleghi il loro contributo e poi ho iniziato a scrivere il codice. Quindi, ho eseguito i test unitari. Alcuni di loro si sono spezzati e quelli che mi hanno segnalato mi hanno indicato esattamente dove dovevo cercare il mio algoritmo per sistemarlo. Dopo aver corretto tutti gli errori dai test delle unità, sono stato in grado di dire che la funzione funziona come dovrebbe. Per lo meno, potrei essere sicuro che questo nuovo algoritmo funzionasse bene come il precedente algoritmo.

Naturalmente, questo non è a prova di proiettile. Se c'è un bug nel mio codice che i test unitari non controllano, allora non lo saprò. Ma naturalmente, lo stesso identico bug avrebbe potuto essere anche nel mio algoritmo più lento. Tuttavia , posso dire con un alto grado di sicurezza che non devo preoccuparmi dell'output sbagliato da quella particolare funzione. I test unitari preesistenti mi hanno consentito di risparmiare ore, forse giorni, per provare a testare il nuovo algoritmo per garantire che fosse corretto.

Quel è il punto di avere test unitari indipendentemente dal TDD - vale a dire, i test unitari lo faranno per te in TDD e al di fuori di TDD lo stesso, quando finisci con il refactoring / mantenere il codice. Naturalmente, questo dovrebbe essere associato a regolari test di regressione, test del fumo, test fuzzy, ecc., Ma il test unit , come afferma il nome, mette alla prova le cose sul più piccolo livello atomico possibile, che ti dà la direzione su dove sono spuntati errori.

Nel mio caso, senza i test unitari esistenti, in qualche modo dovrei trovare un metodo per garantire che l'algoritmo funzioni sempre. Che, alla fine ... suona molto simile a testing dell'unità , vero?

    
risposta data 08.08.2014 - 17:27
fonte
7

Puoi suddividere il codice approssimativamente in 4 categorie:

  1. Cambiamenti semplici e raramente.
  2. Modifiche semplici e frequenti.
  3. Modifiche complesse e raramente.
  4. Modifiche complesse e frequenti.

I test unitari diventano più preziosi (probabilmente per catturare bug importanti) più in basso nell'elenco. Nei miei progetti personali, faccio quasi sempre TDD sulla categoria 4. Nella categoria 3 di solito faccio TDD a meno che il test manuale sia più semplice e veloce. Ad esempio, il codice antialiasing sarebbe complesso da scrivere, ma molto più facile da verificare visivamente rispetto alla scrittura di un test unitario, quindi il test unitario sarebbe valsa la pena solo a me se quel codice fosse cambiato frequentemente. Il resto del mio codice metto solo sotto test unitario dopo aver trovato un bug in quella funzione.

A volte è difficile sapere in anticipo in quale categoria si inserisce un certo blocco di codice. Il valore di TDD è che non si perde per errore nessuno dei complessi test unitari. Il costo di TDD è tutto il tempo che dedichi a scrivere i semplici test unitari. Tuttavia, di solito le persone esperte in un progetto conoscono con un ragionevole grado di certezza in che categoria si inseriscono diverse parti del codice. Se non stai facendo TDD, dovresti almeno provare a scrivere i test più preziosi.

    
risposta data 09.08.2014 - 00:50
fonte
1

Che si tratti di unità, componenti, test di integrazione o di accettazione, la parte importante è che deve essere automatizzata. Non avere test automatici è un errore fatale per qualsiasi tipo di software, dai semplici CRUD ai calcoli più complessi. Il ragionamento è che scrivere i test automatici costa sempre meno della necessità continua di eseguire tutti i test manualmente quando non lo fai, per ordine di grandezza. Dopo averli scritti, devi solo premere un pulsante per vedere se passano o falliscono. I test manuali richiederanno sempre molto tempo per essere eseguiti, e dipenderanno dagli esseri umani (creature viventi che si annoiano, potrebbero non avere l'attenzione e così via) per essere in grado di verificare se i test superano o falliscono. In breve, scrivi sempre test automatici.

Ora, sul motivo per cui il tuo collega potrebbe essere contrario a fare qualsiasi tipo di test unitario senza TDD: è probabilmente perché è più difficile fidarsi dei test scritti dopo il codice di produzione. E se non ti puoi fidare dei tuoi test automatici, vale la pena niente . Dopo il ciclo TDD, è necessario prima eseguire un test fallito (per la giusta ragione) per poter scrivere il codice di produzione per farlo passare (e non di più). Questo processo è essenzialmente test dei test, quindi puoi fidarti di loro. Per non parlare del fatto che scrivere test prima che il codice ti spinga a progettare le tue unità e componenti sia più facilmente testabile (alti livelli di disaccoppiamento, SRP applicato, ecc ...). Anche se, naturalmente, fare TDD richiede disciplina .

Invece, se scrivi per primo tutti i codici di produzione, quando scrivi i test per questo, ti aspetteresti che li superino alla prima esecuzione. Questo è molto problematico, perché potresti aver creato un test che copre il 100% del tuo codice di produzione, senza affermare il comportamento corretto (potrebbe anche non eseguire alcuna asserzione! Ho visto accadere questo ), dal momento che non riesci a vederlo fallendo prima di controllare se sta fallendo per il giusto motivo. Quindi, potresti avere falsi positivi. I falsi positivi alla fine infrangeranno la fiducia nella vostra suite di test, costringendo essenzialmente le persone a ricorrere nuovamente ai test manuali, in modo da avere il costo di entrambi i processi (test di scrittura + test manuali).

Questo significa che deve trovare un altro modo per testare i tuoi test , come fa TDD. Quindi si ricorre al debugging, commentando parti del codice di produzione, ecc., Per essere in grado di fidarsi dei test. Il problema è che il processo di "testare i test" è molto più lento in questo modo. Aggiungendo questo tempo al tempo che impiegherete a eseguire test ad hoc manualmente (perché non avete test automatici mentre state codificando il codice di produzione), nella mia esperienza, si ottiene un processo complessivo molto più lento della pratica TDD "dal libro" (Kent Beck - TDD di Example). Inoltre, sono disposto a fare una scommessa qui e dire che veramente "testare i tuoi test" dopo che sono stati scritti richiede molta più disciplina di TDD.

Quindi forse il tuo team può riconsiderare i "motivi commerciali e non commerciali" per non fare TDD. Nella mia esperienza, le persone tendono a pensare che il TDD sia più lento rispetto alla semplice scrittura di unit test dopo che il codice è stato eseguito. Questa ipotesi è errata, come hai letto sopra.

    
risposta data 09.08.2014 - 19:08
fonte
0

Spesso la qualità dei test dipende dalla loro provenienza. Sono regolarmente colpevole di non aver fatto il "vero" TDD - scrivo un codice per dimostrare che la strategia che mi piacerebbe utilizzare funzioni effettivamente, quindi copro ogni caso che il codice è destinato a supportare con i test successivi. Di solito l'unità di codice è una classe, per darti un'idea generale di quanto lavoro farò felicemente senza copertura del test - cioè, non una grande quantità. Ciò significa che il significato semantico dei test coincide bene con il sistema sotto test nel suo stato 'finito' - perché li ho scritti sapendo quali casi il SUT soddisfa e come li soddisfa.

Al contrario, TDD con la sua politica di refactoring aggressivo tende a test obsoleti almeno con la stessa velocità con cui è possibile scriverli come interfaccia pubblica del sistema sottoposto a test. Personalmente trovo il carico di lavoro mentale sia nella progettazione delle unità funzionali dell'applicazione che mantenendo la semantica dei test che la coprono in sincronia per essere troppo alta per mantenere la mia concentrazione e la manutenzione del test spesso scivola. Il codebase finisce con i test in giro che non testano nulla di valore o sono semplicemente sbagliati. Se hai la disciplina e la capacità mentale per mantenere aggiornato il gruppo di test, con tutti i mezzi, pratica TDD nel modo più rigoroso che desideri. Io no, quindi l'ho trovato meno efficace per questo motivo.

    
risposta data 09.08.2014 - 11:54
fonte
0

In realtà, lo zio Bob ha menzionato un punto molto interessante in uno dei suoi video di Clean Coders. Ha detto che il ciclo Red-Green-Refactor può essere applicato in 2 modi.

Il primo è il metodo TDD convenzionale. Scrivi un test fallito quindi fai passare il test e infine refactoring.

Il secondo modo è scrivere un codice di produzione molto piccolo e seguirlo immediatamente con il suo test unitario e poi refactoring.

L'idea è di andare in molto piccoli passi. Ovviamente si perde la verifica dal codice di produzione che il test è passato dal rosso al verde, ma in alcuni casi in cui lavoravo principalmente con sviluppatori junior che si erano rifiutati anche di cercare di capire il TDD si è dimostrato un po 'efficace.

Ancora una volta ripeto (e questo è stato sottolineato da Uncle Bob) l'idea è di andare in piccoli passi e di testare subito il codice di produzione che è stato appena aggiunto.

    
risposta data 09.08.2014 - 17:02
fonte

Leggi altre domande sui tag