In che modo i test unitari facilitano la progettazione?

43

Il nostro collega promuove la scrittura di unit test in quanto ci aiuta effettivamente a perfezionare le nostre idee di progettazione e refactoring, ma non vedo come. Se sto caricando un file CSV e lo analizzo, in che modo il test dell'unità (convalidando i valori nei campi) mi aiuterà a verificare il mio progetto? Ha menzionato l'accoppiamento e la modularità ecc. Ma per me non ha molto senso - ma non ho molto background teorico, però.

Questo non è lo stesso della domanda che hai contrassegnato come duplicato, sarei interessato ad esempi reali su come questo aiuti, non solo sulla teoria che dice "aiuta". Mi piace la risposta qui sotto e il commento, ma mi piacerebbe saperne di più.

    
posta User039402 09.02.2017 - 19:12
fonte

10 risposte

2

Non solo i test unitari facilitano la progettazione, ma questo è uno dei loro principali vantaggi.

La scrittura di test-first elimina la modularità e la struttura del codice pulita.

Quando scrivi per la prima volta il tuo test di codice, scoprirai che ogni "condizione" di una data unità di codice viene naturalmente spinta alle dipendenze (di solito tramite mock o stub) quando le assumi nel tuo codice.

"La condizione data x, aspetto il comportamento y", spesso diventa uno stub per fornire x (che è uno scenario in cui il test deve verificare il comportamento del componente corrente) e y diventerà un mock, una chiamata a cui verrà verificata alla fine del test (a meno che non sia un "dovrebbe restituire y ", nel qual caso il test verificherà solo esplicitamente il valore di ritorno).

Quindi, una volta che questa unità si è comportata come specificato, passerai alla scrittura delle dipendenze (per x e y ) che hai scoperto.

Ciò rende la scrittura di codice pulito e modulare un processo molto semplice e naturale, dove altrimenti è spesso facile sfocare le responsabilità e accoppiare i comportamenti insieme senza rendersene conto.

Scrittura di test successivi ti dirà quando il tuo codice è strutturato in modo scadente.

Quando scrivere i test per un pezzo di codice diventa difficile perché ci sono troppe cose da mozzare o da mockare, o perché le cose sono troppo strettamente accoppiate, sai che hai miglioramenti da apportare al tuo codice.

Quando "cambiare i test" diventa un fardello perché ci sono così tanti comportamenti in una singola unità, sai che hai dei miglioramenti da fare nel tuo codice (o semplicemente nel tuo approccio alla scrittura dei test - ma questo di solito non è il caso la mia esperienza).

Quando i tuoi scenari diventano troppo complicati ("se x e y e z poi ...") perché devi astrarre di più, sai che hai miglioramenti da apportare al tuo codice.

Quando finisci con gli stessi test in due fixture diverse a causa della duplicazione e della ridondanza, sai che hai miglioramenti da apportare al tuo codice.

Ecco un eccellente discorso di Michael Feathers che dimostra la stretta relazione tra testabilità e design nel codice (originariamente pubblicato da displayName nei commenti). Il discorso affronta anche alcune lamentele e idee sbagliate comuni sul buon design e testabilità in generale.

    
risposta data 11.02.2017 - 10:45
fonte
103

Il bello dei test unitari è che ti permettono di usare il tuo codice su come gli altri programmatori useranno il tuo codice.

Se il tuo codice è scomodo per il test dell'unità, probabilmente sarà imbarazzante da usare. Se non è possibile iniettare le dipendenze senza saltare attraverso i cerchi, il codice sarà probabilmente poco flessibile da utilizzare. E se hai bisogno di dedicare un sacco di tempo alla configurazione dei dati o al calcolo dell'ordine in cui eseguire le operazioni, probabilmente il codice in prova ha troppi accessi e sarà un problema con cui lavorare.

    
risposta data 09.02.2017 - 19:15
fonte
31

Mi ci è voluto un po 'di tempo per rendermene conto, ma il vero vantaggio (modifica: per me, la tua milizia può variare) di fare test driven development ( using unit test) è che tu devi fare il design dell'API in primo piano !

Un tipico approccio allo sviluppo consiste nel capire in primo luogo come risolvere un dato problema, e con quella conoscenza e progettazione iniziale di implementazione in qualche modo per invocare la soluzione. Questo potrebbe dare dei risultati piuttosto interessanti.

Quando fai il TDD devi prima scrivere il codice che usa la tua soluzione. Parametri di input e output attesi per assicurarti che sia giusto. Ciò a sua volta richiede di capire cosa è effettivamente necessario per farlo, in modo da poter creare test significativi. Allora e solo allora realizzi la soluzione. È anche la mia esperienza che quando si sa esattamente cosa si suppone che il codice debba raggiungere, diventa più chiaro.

Quindi, dopo i test unitari dell'implementazione, ti aiuta a garantire che il refactoring non interrompa la funzionalità e fornisca documentazione su come utilizzare il tuo codice (che sai essere corretto quando il test è passato!). Ma questi sono secondari - il vantaggio maggiore è la mentalità nella creazione del codice in primo luogo.

    
risposta data 10.02.2017 - 00:09
fonte
6

Sono d'accordo al 100% che i test unitari aiutano "ci aiutano a perfezionare le nostre idee di progettazione e refactoring".

Sono di due menti se ti aiutano a fare il progetto iniziale . Sì, rivelano evidenti difetti e ti costringono a pensare a "come posso rendere testabile il codice"? Ciò dovrebbe comportare un minor numero di effetti collaterali, una configurazione e configurazioni più semplici, ecc.

Tuttavia, nella mia esperienza, i test unitari troppo semplicistici, scritti prima di capire veramente quale dovrebbe essere il design, (ammettiamolo, è un'esagerazione del TDD hard-core, ma troppo spesso i programmatori scrivono un test prima che pensino molto) portare a modelli di dominio anemici che espongono troppi interni.

La mia esperienza con TDD è stata diversi anni fa, quindi sono interessato a sapere quali tecniche più recenti potrebbero aiutare nella scrittura di test che non pregiudicano troppo il progetto sottostante. Grazie.

    
risposta data 10.02.2017 - 01:20
fonte
5

Il test unitario consente di vedere come funzionano le interfacce tra le funzioni e spesso fornisce informazioni su come migliorare sia la progettazione locale che la progettazione generale. Inoltre, se sviluppi i tuoi test unitari mentre sviluppi il tuo codice, hai una suite di test di regressione pronta all'uso. Non importa se stai sviluppando un'interfaccia utente o una libreria di backend.

Una volta che il programma è stato sviluppato (con test di unità), mentre i bug sono stati scoperti, puoi aggiungere test per confermare che i bug sono corretti.

Uso TDD per alcuni dei miei progetti. Ho fatto un grande sforzo nel creare esempi che estraggo da libri di testo o da documenti ritenuti corretti e testare il codice che sto sviluppando utilizzando questi esempi. Tutti i fraintendimenti che ho riguardo ai metodi diventano molto evidenti.

Tendo ad essere un po 'più sciolto di alcuni dei miei colleghi, poiché non mi interessa se il codice è stato scritto per primo o il test è stato scritto per primo.

    
risposta data 09.02.2017 - 19:57
fonte
5

Quando si desidera testare l'unità del parser che rileva la delimitazione del valore in modo corretto, è possibile passare una riga da un file CSV. Per rendere il tuo test diretto e breve potresti voler testarlo attraverso un metodo che accetta una riga.

Questo ti farà automaticamente separare la lettura delle linee dalla lettura dei singoli valori.

A un altro livello potresti non voler inserire tutti i tipi di file CSV fisici nel tuo progetto di test, ma fare qualcosa di più leggibile, semplicemente dichiarando una grande stringa CSV all'interno del test per migliorare la leggibilità e l'intento del test. Questo ti porterà a disaccoppiare il tuo parser da qualsiasi I / O che dovresti fare altrove.

Solo un esempio di base, inizia a esercitarti, sentirai la magia ad un certo punto (ho).

    
risposta data 09.02.2017 - 20:28
fonte
3

In parole povere, la scrittura di unit test aiuta a svelare difetti nel codice.

Questa spettacolare guida alla scrittura di codice verificabile , scritto da Jonathan Wolter, Russ Ruffer e Miško Hevery, contiene numerosi esempi di come i difetti nel codice, che capita di inibire i test, impediscano anche un facile riutilizzo e flessibilità dello stesso codice. Quindi, se il tuo codice è testabile, è più facile da usare. La maggior parte dei "costumi" sono consigli incredibilmente semplici che migliorano notevolmente la progettazione del codice ( Iniezione delle dipendenze FTW).

Ad esempio: è molto difficile verificare se il metodo computeStuff funziona correttamente quando la cache inizia a sfrattare roba. Questo perché devi aggiungere manualmente crap alla cache fino a quando "bigCache" è quasi pieno.

public OopsIHardcoded {

   Cache cacheOfExpensiveComputations;

   OopsIHardcoded() {
       this.cacheOfExpensiveComputation = buildBigCache();
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

Tuttavia, quando usiamo l'integrazione delle dipendenze è molto più facile testare se il metodo computeStuff funziona correttamente quando la cache inizia a sfrattare roba. Tutto ciò che facciamo è creare un test in cui chiamiamo new HereIUseDI(buildSmallCache()); Notice, abbiamo un controllo più sfumato dell'oggetto e paga immediatamente i dividendi.

public HereIUseDI {

   Cache cacheOfExpensiveComputations;

   HereIUseDI(Cache cache) {
       this.cacheOfExpensiveComputation = cache;
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

Benefici simili possono essere ottenuti quando il nostro codice richiede dati che di solito sono conservati in un database ... basta inserire ESATTAMENTE i dati che ti servono.

    
risposta data 09.02.2017 - 21:41
fonte
0

A seconda di cosa si intende per "Test unità", non penso che i test Unità di livello più basso facilitino un buon progetto tanto quanto un livello leggermente più alto di integrazione test - test che testano che un gruppo di attori (classi, funzioni, qualsiasi cosa) nel codice combini correttamente per produrre un sacco di comportamenti desiderabili che sono stati concordati tra il team di sviluppo e il proprietario del prodotto.

Se riesci a scrivere test a quei livelli, ti spinge verso la creazione di un codice bello, logico, simile a API che non richiede molte dipendenze pazze: il desiderio di avere una semplice configurazione di prova ti porterà naturalmente a non avere molte dipendenze pazzesche o un codice strettamente accoppiato.

Non commettere errori però - I test delle unità possono portare a una cattiva progettazione e a un buon design . Ho visto gli sviluppatori prendere un po 'di codice che ha già un buon design logico e una singola preoccupazione, e separarlo e introdurre più interfacce puramente a scopo di test, e di conseguenza rendere il codice meno leggibile e più difficile da modificare , e forse anche avere più bug se lo sviluppatore ha deciso che avere molti test unitari di basso livello significa che non devono avere test di livello superiore. Un particolare esempio preferito è un bug che ho risolto nel caso in cui esistesse un codice molto "discontinuo", "testabile" relativo all'ottenimento di informazioni dentro e fuori dagli appunti. Tutto suddiviso e disaccoppiato a livelli molto piccoli di dettagli, con molte interfacce, un sacco di finte test e altre cose divertenti. Unico problema: non c'era alcun codice che effettivamente interagisse con il meccanismo degli appunti del sistema operativo, quindi il codice in produzione in realtà non ha fatto nulla.

I test unitari possono sicuramente guidare il tuo design, ma non ti guidano automagicamente verso un buon design. Hai bisogno di idee su cosa sia un buon design che vada oltre "questo codice è testato, quindi è testabile, quindi è buono '.

Ovviamente se sei una di quelle persone per le quali "test unitari" significa "qualsiasi test automatizzato che non viene guidato attraverso l'interfaccia utente", allora alcuni di questi avvertimenti potrebbero non essere così rilevanti - come ho detto, io pensa che quei test di integrazione di livello superiore sono spesso i più utili quando si tratta di guidare il tuo design.

    
risposta data 11.02.2017 - 18:15
fonte
-2

I test unitari possono aiutare con il refactoring quando il nuovo codice passa tutti i vecchi test.

Supponiamo che tu abbia implementato un bubblesort perché eri di fretta e non preoccupato per le prestazioni, ma ora vuoi un quicksort perché i dati si allungano. Se passano tutti i test, le cose sembrano buone.

Ovviamente i test devono essere completi per fare in modo che funzioni. Nel mio esempio, i tuoi test potrebbero non coprire la stabilità perché non era un problema con bubblesort.

    
risposta data 11.02.2017 - 13:16
fonte
-3

Ho trovato i test unitari di maggior valore per facilitare la manutenzione a lungo termine di un progetto. Quando torno a un progetto dopo mesi e non ricordo gran parte dei dettagli, eseguire test mi impedisce di rompere le cose.

    
risposta data 10.02.2017 - 00:45
fonte

Leggi altre domande sui tag