Non capisco come TDD mi aiuti a ottenere un buon design se ho bisogno di un design per iniziare a testarlo


50

Sto cercando di avvolgere la mia testa attorno a TDD, in particolare la parte di sviluppo. Ho guardato alcuni libri, ma quelli che ho trovato affrontano principalmente la parte di test: la storia di NUnit, perché il test è buono, rosso / verde / refactor e come creare un calcolatore di stringhe.

Roba buona, ma questo è "solo" Unit Testing, non TDD. In particolare, non capisco come TDD mi aiuti a ottenere un buon design se ho bisogno di un Design per iniziare a testarlo.

Per illustrare, immagina questi 3 requisiti:

  • Un catalogo deve avere un elenco di prodotti
  • Il catalogo dovrebbe ricordare quali prodotti sono stati visualizzati da un utente
  • Gli utenti dovrebbero essere in grado di cercare un prodotto

A questo punto, molti libri tirano fuori un cappello magico da un cappello e si tuffano semplicemente in "Testing del ProductService", ma non spiegano come sono arrivati ​​alla conclusione che esiste un ProductService in primo luogo. Questa è la parte "Sviluppo" in TDD che sto cercando di capire.

Deve esserci un progetto esistente, ma cose al di fuori dei servizi di entità (vale a dire: c'è un prodotto, quindi dovrebbe esserci un servizio di servizio) non si trova da nessuna parte (ad esempio, il secondo requisito richiede che io abbia un concetto di un Utente, ma dove metterei la funzionalità per ricordare? E la ricerca è una funzionalità del ProductService o un servizio di ricerca separato? Come faccio a sapere quale dovrei scegliere?)

Secondo SOLID , avrei bisogno di un UserService, ma se progetto un sistema senza TDD, potrei finire con un sacco di servizi a metodo singolo. TDD non è destinato a farmi scoprire il mio design in primo luogo?

Sono uno sviluppatore .net, ma funzionerebbero anche le risorse Java. Sento che non sembra esserci una vera e propria applicazione o libro di esempio che tratta una vera linea di applicazione aziendale. Qualcuno può fornire un chiaro esempio che illustra il processo di creazione di un progetto utilizzando TDD?


2
TDD è solo una parte dell'intera metodologia di sviluppo. Ovviamente dovrai utilizzare un qualche tipo di design (in anticipo o meglio evolutivo) per mettere insieme tutto.
Euforico,

3
@gnat: è un'indagine sul perché i libri TDD non rendono più chiaro il processo di progettazione.
Robert Harvey,

4
@gnat: era la tua modifica, non la mia. :) Vedi la mia modifica al titolo della domanda e al corpo.
Robert Harvey,

9
Se hai letto il lavoro di Robert C. Martin o forse hai visto uno dei suoi video, vedrai che spesso ha in mente un disegno ma non è sposato. Egli è convinto che la sua nozione di pre-concepito il modello giusto emergerà dalle sue prove, ma non costringerlo a. E alla fine, a volte quel design lo fa, a volte no. Il punto qui è che la tua esperienza precedente ti guiderà, ma i test dovrebbero guidarti. I test dovrebbero essere in grado di sviluppare o sfatare il tuo progetto.
Anthony Pegram,

3
Quindi non si tratta solo di test, ma di design. Solo che non ti sta davvero aiutando con il design, ma quanto ti aiuta a convalidare il design. Ma non è questo! @ # $ Ing test?
Erik Reppen,

Risposte:


17

L'idea di TDD è di iniziare con i test e lavorare da quello. Quindi, per fare il tuo esempio di "Un catalogo deve avere un elenco di prodotti" potrebbe essere considerato avere un test di "Cerca prodotti nel catalogo" e quindi questo è il primo test. Ora, cosa contiene un catalogo? Cosa contiene un prodotto? Questi sono i prossimi pezzi e l'idea è quella di mettere insieme alcuni pezzi che sarebbero qualcosa come un ProductService che nascerà dal superamento del primo test.

L'idea di TDD è iniziare con un test e quindi scrivere il codice che fa passare quel test come primo punto. I test unitari fanno parte di questo sì, ma non stai guardando l'immagine generale che si forma iniziando con i test e quindi scrivendo il codice in modo che non ci siano punti ciechi a questo punto poiché non c'è ancora alcun codice.


Test Driven Development Tutorial in cui le diapositive 20-22 sono quelle chiave. L'idea è di sapere cosa dovrebbe fare la funzionalità di conseguenza, scrivere un test per esso e quindi creare una soluzione. La parte di progettazione varierà in base a ciò che è richiesto o meno. Un punto chiave è usare TDD dall'inizio piuttosto che provare a introdurre tardi in un progetto. Se inizi con i test prima questo può aiutare e probabilmente vale la pena notare in un certo senso. Se si tenta di aggiungere i test in un secondo momento, diventa qualcosa che può essere rimandato o ritardato. Anche le diapositive successive possono essere utili.


Un vantaggio principale di TDD è che, iniziando con i test, inizialmente non si è bloccati in un progetto. Pertanto, l'idea è quella di costruire i test e creare il codice che li supererà come metodologia di sviluppo. Un Big Design Up Front può causare problemi in quanto ciò dà l'idea di bloccare le cose in posizione, il che rende il sistema in costruzione meno agile alla fine.


Robert Harvey ha aggiunto questo nei commenti che vale la pena affermare nella risposta:

Sfortunatamente penso che questo sia un malinteso comune sul TDD: non è possibile far crescere un'architettura software semplicemente scrivendo unit test e facendoli passare. La scrittura di unit test influenza il design, ma non crea il design. Devi farlo.


31
@MichaelStum: Sfortunatamente penso che questo sia un malinteso comune sul TDD: non è possibile far crescere un'architettura software semplicemente scrivendo unit test e facendoli passare. La scrittura di unit test influenza il design, ma non crea il design. Devi farlo.
Robert Harvey,

4
@RobertHarvey, JimmyHoffa: se potessi votare i tuoi commenti 100 volte, lo farei!
Doc Brown,

9
@Robert Harvey: Sono contento che tu abbia scritto di questo malinteso comune: sento troppo spesso che uno deve semplicemente sedersi e scrivere tutti i tipi di test unitari e un progetto "emergerà" spontaneamente. E se il tuo progetto non è corretto, è perché non hai scritto abbastanza unit test. Sono d'accordo con te sul fatto che i test sono uno strumento per specificare e verificare i requisiti per il tuo progetto, ma "devi fare" il design da solo. Sono totalmente d'accordo.
Giorgio,

4
@Giorgo, RobertHarvey: +1000 a RobertHarvey anche da me. Sfortunatamente, questo malinteso è abbastanza comune che alcuni professionisti "esperti" TDD / Agili credono che sia vero. Come per esempio, fanno finta che tu possa "evolvere" un risolutore di sudoku da TDD, senza conoscenza del dominio o analisi di alcun tipo . Mi chiedo se Ron Jeffries abbia mai pubblicato un follow-up sui limiti del TDD o spiegato perché improvvisamente ha interrotto il suo esperimento senza conclusioni o lezioni apprese.
Andres F.

3
@Andres F: conosco la storia del sudoku e penso che sia molto interessante. Penso che alcuni sviluppatori commettano l'errore di pensare che uno strumento (ad es. TDD o SCRUM) possa sostituire la conoscenza del dominio e i propri sforzi e si aspettano che applicando meccanicamente un metodo particolare un buon software "magicamente" emergerà. Spesso sono persone a cui non piace passare troppo tempo in analisi e progettazione e preferiscono codificare qualcosa direttamente. Per loro, seguire una particolare metodologia è un alibi per non fare una progettazione adeguata. Ma questo è IMHO un uso improprio di TDD.
Giorgio,

8

Per quello che vale, TDD mi aiuta a raggiungere il miglior design più rapidamente di non fare TDD. Probabilmente arriverei al miglior design con o senza di esso. Ma quel tempo che avrei passato a pensarci su e prendere alcune pugnalate al codice è invece passato a scrivere test. Ed è meno tempo. Per me. Non per tutti. E, anche se impiegasse la stessa quantità di tempo, mi lascerebbe con una serie di test, in modo che il refactoring sia più sicuro, portando a un codice ancora migliore lungo la linea.

Come lo fa?

Innanzitutto, mi incoraggia a pensare a ogni classe come servizio a un codice client. Il codice migliore deriva dal pensare a come il codice chiamante vuole usare l'API piuttosto che preoccuparsi di come dovrebbe apparire il codice stesso.

In secondo luogo, mi impedisce di scrivere troppa complessità ciclometrica in un metodo, mentre ci sto pensando. Ogni percorso aggiuntivo attraverso un metodo tenderà a raddoppiare il numero di test che devo fare. La pigrizia impone che dopo aver aggiunto troppa logica e devo scrivere 16 test per aggiungere una condizione, è tempo di estrarne un po 'in un altro metodo / classe e testarlo separatamente.

È davvero così semplice. Non è uno strumento di progettazione magica.


6

Sto cercando di avvolgere la mia testa attorno a TDD ... Per illustrare, immagina questi 3 requisiti:

  • Un catalogo deve avere un elenco di prodotti
  • Il catalogo dovrebbe ricordare quali prodotti sono stati visualizzati da un utente

Questi requisiti dovrebbero essere riformulati in termini umani. Chi vuole sapere quali prodotti l'utente ha precedentemente visualizzato? L'utente? Un venditore?

  • Gli utenti dovrebbero essere in grado di cercare un prodotto

Come? Per nome? Per marchio? Il primo passo nello sviluppo guidato dai test è definire un test, ad esempio:

browse to http://ourcompany.com
enter "cookie" in the product search box
page should show "chocolate-chip cookies" and "oatmeal cookies"

>

A questo punto, molti libri tirano fuori un cappello magico da un cappello e si tuffano semplicemente in "Testing del ProductService", ma non spiegano come sono arrivati ​​alla conclusione che esiste un ProductService in primo luogo.

Se questi sono gli unici requisiti, certamente non farei un salto per creare un ProductService. Potrei creare una pagina Web molto semplice con un elenco di prodotti statici. Funzionerebbe perfettamente fino a quando non si arriva ai requisiti per aggiungere ed eliminare prodotti. A quel punto potrei decidere che è più semplice utilizzare un database relazionale e un ORM e creare una classe di prodotto mappata su una singola tabella. Ancora nessun servizio di assistenza. Classi come ProductService verranno create quando e se sono necessarie. Potrebbero esserci più richieste Web che devono eseguire le stesse query o gli stessi aggiornamenti. Quindi verrà creata la classe ProductService per impedire la duplicazione del codice.

In breve, TDD guida il codice da scrivere. La progettazione avviene quando si effettuano scelte di implementazione, quindi si converte il codice in classi per eliminare la duplicazione e controllare le dipendenze. Quando aggiungi il codice, dovrai creare nuove classi per mantenere il codice SOLID. Tuttavia, non è necessario decidere in anticipo che sarà necessaria una classe Product e una ProductService. Potresti scoprire che la vita va perfettamente bene solo con una classe di prodotti.


OK, no ProductServiceallora. Ma come ti ha detto TDD che avevi bisogno di un database e di un ORM?
Robert Harvey,

4
@Robert: no. È una decisione di progettazione, basata sul mio giudizio sul modo più efficace per soddisfare il requisito. Ma la decisione potrebbe cambiare.
Kevin Cline

1
Un buon design non verrà mai prodotto come effetto collaterale di un processo arbitrario. Avere un sistema o un modello su cui lavorare e inquadrare le cose è fantastico, ma il test-first-TDD, IMO, colpisce un conflitto di interessi vendendo anche se stesso come qualcosa che garantirà alle persone che non saranno morso inaspettatamente dagli effetti collaterali del male codice che non avrebbe dovuto accadere in primo luogo. Il design richiede riflessione, consapevolezza e riflessione. Non impari quelli dalla potatura dei sintomi scoperti automaticamente dall'albero. Li impari scoprendo come evitare i rami mutanti malvagi in primo luogo.
Erik Reppen,

Penso che il test 'aggiunga un prodotto; riavviare il computer e riavviare il sistema; il prodotto aggiunto dovrebbe essere ancora visibile. " mostra da dove proviene la necessità di una sorta di database (ma che potrebbe essere comunque un file flat o XML).
yatima2975,

3

Altri potrebbero non essere d'accordo, ma per me molte delle nuove metodologie si basano sul presupposto che lo sviluppatore farà la maggior parte di ciò che le metodologie più vecchie spiegano solo per abitudine o orgoglio personale, che lo sviluppatore di solito sta facendo qualcosa che è abbastanza ovvio a loro, e il lavoro è incapsulato in un linguaggio pulito o nelle parti più pulite di un linguaggio un po 'confuso in modo da poter fare tutto il business dei test.

Alcuni esempi in cui mi sono imbattuto in questo in passato:

  • Prendi un sacco di appaltatori specializzati e dì loro che il loro team è Agile and Test First. Spesso non hanno altra abitudine se non quella di lavorare su specifiche e non hanno alcuna preoccupazione per la qualità del lavoro fintanto che dura abbastanza a lungo per completare il progetto.

  • Prova prima a fare qualcosa di nuovo, trascorri molto del tempo a strappare i test quando trovi che vari approcci e interfacce sono una schifezza.

  • Codifica qualcosa di basso livello o verrai schiaffeggiato per mancanza di copertura o scrivi molti test che non equivalgono a molto valore perché non puoi deridere i comportamenti sottostanti a cui sei legato.

  • Qualsiasi situazione in cui non si dispone in anticipo di una meccanica sufficiente per aggiungere una funzionalità testabile senza scrivere prima un gruppo di bit non verificabili sottostanti, come il sottosistema del disco o un'interfaccia di comunicazione a livello di tcpip.

Se stai facendo TDD e funziona per te, va bene per te, ma ci sono un sacco di cose (interi lavori o fasi di un progetto) là fuori dove questo semplicemente non aggiunge valore.

Il tuo esempio sembra che tu non sia nemmeno ancora a un progetto, quindi o devi avere una conversazione di architettura o stai prototipando. Secondo me, devi prima superarne alcune.


1

Sono convinto che TDD sia un approccio molto prezioso alla progettazione dettagliata del sistema, ovvero le API e il modello a oggetti. Tuttavia, per arrivare al punto in un progetto in cui inizieresti ad usare TDD, devi avere il quadro generale del design già modellato in qualche modo e devi avere il quadro generale dell'architettura già modellato in qualche modo. @ user414076 parafrasa Robert Martin con un'idea di design in mente, ma non esserne sposato. Esattamente. Conclusione: TDD non è l'unica attività di progettazione in corso, è il modo in cui i dettagli del progetto vengono realizzati. TDD deve essere preceduto da altre attività di progettazione e adattarsi a un approccio globale (come Agile) che affronti il ​​modo in cui la progettazione generale viene creata ed evoluta.

Cordiali saluti - due libri che consiglio sull'argomento che forniscono esempi tangibili e realistici:

Software in crescita orientato agli oggetti, guidato da test - spiega e fornisce un esempio completo del progetto. Questo è un libro sul design, non sui test . Il test viene utilizzato come mezzo per specificare il comportamento previsto durante le attività di progettazione.

sviluppo guidato dai test Una guida pratica - una guida lenta e passo-passo attraverso lo sviluppo di un'app completa, anche se piccola.


0

Il TTD guida la scoperta del progetto in caso di esito negativo del test, non di successo, pertanto è possibile testare incognite e ripetere nuovamente il test in quanto le incognite vengono esposte alla fine portando a una serie completa di test unitari - una cosa molto bella da avere per la manutenzione continua e una cosa molto difficile da provare retrofit dopo che il codice è stato scritto / rilasciato.

Ad esempio, un requisito potrebbe essere che l'input può essere in diversi formati, non tutti sono ancora noti. Utilizzando TDD dovresti prima scrivere un test per verificare che venga fornito l'output appropriato dato qualsiasi formato di input. Ovviamente questo test fallirà, quindi scrivi il codice per gestire i formati noti e ripetere il test. Poiché i formati sconosciuti vengono esposti attraverso la raccolta dei requisiti, vengono scritti nuovi test prima che il codice venga scritto, anche questi dovrebbero fallire. Quindi viene scritto un nuovo codice per supportare i nuovi formati e tutti i test vengono rieseguiti riducendo le possibilità di regressione.

È anche utile pensare al fallimento dell'unità come codice "incompiuto" anziché codice "rotto". TDD consente unità non finite (guasti previsti), ma riduce il verificarsi di unità rotte (guasti imprevisti).


1
Sono d'accordo sul fatto che si tratta di un flusso di lavoro valido, ma non spiega in realtà come un'architettura di alto livello possa emergere da tale flusso di lavoro.
Robert Harvey,

1
Bene, un'architettura di alto livello come il modello MVC, non emergerà dal solo TDD. Ma ciò che può emergere da TDD è un codice progettato per essere facilmente testabile, che è una considerazione di progettazione in sé.
Daniel Pereira,

0

Nella domanda si afferma:

... molti libri tirano fuori un cappello magico da un cappello e si tuffano semplicemente in "Test del servizio di servizio", ma non spiegano come sono arrivati ​​alla conclusione che esiste un servizio di servizio in primo luogo.

Sono arrivati ​​a questa conclusione pensando a come avrebbero testato questo prodotto. "Che tipo di prodotto fa questo?" "Bene, potremmo creare un servizio". "Ok, scriviamo un test per un tale servizio"


0

Una funzionalità può avere molti design e TDD non ti dirà completamente quale è la migliore. Inoltre, se i test ti aiuteranno a costruire un codice più modulare, ti porteranno anche a costruire moduli adatti ai requisiti dei test e non alla realtà produttiva. Quindi devi capire dove stai andando e come le cose dovrebbero adattarsi all'intera immagine. In altre parole, ci sono requisiti funzionali e non funzionali, non dimenticare l'ultimo.

Per quanto riguarda il design, mi riferisco ai libri di Robert C. Martin (Agile Development) ma anche a Patterns of Enterprise Application Architecture e Domain Driver Design di Martin Fowler. Il successivo è particolarmente sistematico nell'estrarre le Entità e le Relazioni dai requisiti.

Quindi, quando ti rendi conto delle opzioni disponibili su come gestire tali entità, puoi alimentare il tuo approccio TDD.


0

TDD non è destinato a farmi scoprire il mio design in primo luogo?

No.

Come puoi testare qualcosa che non hai progettato prima?

Per illustrare, immagina questi 3 requisiti:

  • Un catalogo deve avere un elenco di prodotti
  • Il catalogo dovrebbe ricordare quali prodotti sono stati visualizzati da un utente
  • Gli utenti dovrebbero essere in grado di cercare un prodotto

Questi non sono requisiti, queste sono definizioni di dati. Non so quale sia l'attività del tuo software, ma non è probabile che gli analisti parlino in questo modo.

Devi sapere quali sono gli invarianti del tuo sistema.

Un requisito sarebbe qualcosa del tipo:

  • Un cliente può ordinare un determinato quantitativo di prodotto, se è disponibile a magazzino.

Quindi, se questo è l'unico requisito, potresti avere una classe come:

public class Product {

  private int quantity;

  public Product(int initialQuantity) {
    this.quantity = initialQuantity;
  }

  public void order(int quantity) {
    // To be implemented.
  }

}

Quindi, usando TDD, dovresti scrivere un test case prima di implementare il metodo order ().

public void ProductTest() {

    public void testCorrectOrder() {

        Product p = new Product(10);
        p.order(3);
        p.order(4);

    }

    @Expect(ProductOutOfStockException)
    public void testIncorrectOrder() {

        Product p = new Product(10);
        p.order(7);
        p.order(4);

    }

}

Quindi il secondo test fallirà, quindi puoi implementare il metodo order () nel modo che preferisci.


0

Hai ragione TDD comporterà una buona implementazione di un determinato progetto. Non aiuterà il tuo processo di progettazione.


Tuttavia ti dà la rete di sicurezza per migliorare il design senza rompere il codice di lavoro. Questo è il refactoring che molte persone saltano.
Adrian Schneider,

-3

TDD aiuta molto, tuttavia c'è una parte importante nello sviluppo del software. Lo sviluppatore dovrebbe ascoltare il codice che viene scritto. Il refactoring è la terza parte del ciclo TDD. Questo è il passo principale in cui lo sviluppatore dovrebbe concentrarsi e pensare prima di passare al prossimo test rosso. C'è qualche duplicazione? I principi SOLID sono applicati? Che dire di alta coesione e basso accoppiamento? E i nomi? Dai un'occhiata più da vicino al codice che emerge dai test e vedi se c'è qualcosa che deve essere cambiato, riprogettato. Domanda che il codice e il codice ti diranno, come vuole essere progettato. Di solito scrivo set di test multipli, esamino quell'elenco e creo il primo semplice disegno, non è necessario che sia "finale", di solito non lo è, perché è cambiato quando si aggiungono nuovi test. Ecco dove arriva il design.

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.