Sta creando gli oggetti che pensi di aver bisogno di ok in un primo test in TDD


15

Sono abbastanza nuovo in TDD e ho problemi a creare il mio primo test prima di qualsiasi codice di implementazione. Senza alcun framework per il codice di implementazione, sono libero di scrivere il mio primo test come voglio, ma sembra sempre uscito contaminato dal mio modo di pensare Java / OO al problema.

Ad esempio nel mio Github ConwaysGameOfLifeExample il primo test che ho scritto (rule1_zeroNeighbours) ho iniziato creando un oggetto GameOfLife che non era stato ancora implementato; chiamato un metodo set che non esisteva, un metodo step che non esisteva, un metodo get che non esisteva e quindi usato un'asserzione.

I test si sono evoluti quando ho scritto più test e refactored, ma originariamente sembrava qualcosa del genere:

@Test
public void rule1_zeroNeighbours()
{
    GameOfLife gameOfLife = new GameOfLife();
    gameOfLife.set(1, 1, true);
    gameOfLife.step();
    assertEquals(false, gameOfLife.get(1, 1));
}

Mi è sembrato strano mentre stavo forzando la progettazione dell'implementazione in base a come avevo deciso in questa fase iniziale di scrivere questo primo test.

Nel modo in cui capisci TDD, va bene? Mi sembra di seguire i principi TDD / XP in quanto i miei test e l'implementazione si sono evoluti nel tempo con il refactoring, quindi se questo progetto iniziale si fosse rivelato inutile sarebbe stato aperto al cambiamento, ma mi sembra di forzare una direzione sul soluzione iniziando in questo modo.

In che altro modo le persone usano TDD? Avrei potuto attraversare una maggiore iterazione del refactoring iniziando senza alcun oggetto GameOfLife, solo primitivi e metodi statici ma sembra troppo forzato.


5
TDD non sostituisce né un'attenta pianificazione né un'attenta selezione del modello di progettazione. Detto questo, prima di aver scritto qualsiasi implementazione per soddisfare le prime poche righe di test è un momento molto migliore rispetto a quando hai scritto dipendenze per riconoscere che il tuo piano è stupido, che hai scelto lo schema sbagliato, o anche solo che è imbarazzante o confuso invocare una classe nel modo richiesto dal test.
svidgen,

Risposte:


9

Mi è sembrato strano mentre stavo forzando la progettazione dell'implementazione in base a come avevo deciso in questa fase iniziale di scrivere questo primo test.

Penso che questo sia il punto chiave nella tua domanda, se questo sia desiderabile dipende dal fatto che ti appoggi all'idea di codeninja che dovresti progettare in anticipo quindi usare TDD per compilare l'implementazione, o l'idea di Durron che i test dovrebbero essere coinvolti in espellendo la progettazione e la realizzazione.

Penso che quale di questi tu preferisca (o dove cadi nel mezzo) è qualcosa che devi scoprire da solo come preferenza. È utile comprendere i pro ei contro di ogni approccio. Probabilmente ce ne sono molti, ma direi che i principali sono:

Design all'avanguardia

  • Per quanto un TDD sia efficace nel guidare il design, non è perfetto. TDing senza una destinazione concreta in mente a volte può incorrere in vicoli ciechi, e almeno alcuni di questi vicoli ciechi avrebbero potuto essere evitati con un po 'di anticipo pensando a dove si vuole finire. Questo articolo del blog espone questo argomento usando l'esempio dei kata di numeri romani e ha un'implementazione finale piuttosto piacevole da mostrare per questo.

Pro Test-Driving Design

  • Costruendo la tua implementazione attorno a un client del tuo codice (i tuoi test), ottieni l'adesione a YAGNI praticamente gratis, a condizione che non inizi a scrivere casi di test non necessari. Più in generale, ottieni un'API progettata in base al suo utilizzo da parte di un consumatore, che è in definitiva ciò che desideri.

  • L'idea di disegnare un sacco di diagrammi UML prima di scrivere qualsiasi codice e poi semplicemente riempire gli spazi vuoti è piacevole, ma raramente realistica. Nel Codice completo di Steve McConnell, il design è noto come un "problema malvagio" - un problema che non puoi comprendere appieno senza prima almeno risolverlo parzialmente. Abbinalo al fatto che il problema di base stesso può cambiare a causa delle mutate esigenze e questo modello di design inizia a sembrare un po 'senza speranza. La guida di prova ti consente di mordere un pezzo di lavoro alla volta - nella progettazione, non solo di implementazione - e sapere che almeno per la durata della vita di diventare rosso per diventare verde, quell'attività sarà comunque aggiornata e pertinente.

Per quanto riguarda il tuo esempio particolare, come dice durron, se avessi adottato un approccio per scacciare il progetto scrivendo il test più semplice, consumando l'interfaccia minima possibile, probabilmente inizieresti con un'interfaccia più semplice di quella nel tuo frammento di codice .


Il link è stato un'ottima lettura Ben. Grazie per averlo condiviso.
RubberDuck,

1
@RubberDuck Prego! In realtà non sono completamente d'accordo, ma penso che faccia un ottimo lavoro nel sostenere questo punto di vista.
Ben Aaronson,

1
Non sono sicuro di farlo, ma fa bene il caso. Penso che la risposta giusta sia da qualche parte nel mezzo. Devi avere un piano, ma sicuramente se i tuoi test ti sembrano imbarazzanti, riprogetta. Comunque ... ++ buon vecchio fagiolo di spettacolo.
RubberDuck,

17

Per poter scrivere il test in primo luogo, è necessario progettare l'API che si intende implementare. Hai già iniziato con il piede sbagliato scrivendo il test per creare l' intero GameOfLife oggetto e utilizzandolo per implementare il test.

Dal test pratico dell'unità con JUnit e Mockito :

All'inizio potresti sentirti a disagio nello scrivere qualcosa che non è nemmeno lì. Richiede una leggera modifica delle tue abitudini di codifica, ma dopo un po 'di tempo ti accorgerai che è un'ottima opportunità di progettazione. Scrivendo prima i test, hai la possibilità di creare un'API che può essere utilizzata da un client. Il tuo test è il primo client di un'API appena nata. Questo è il vero scopo di TDD: la progettazione di un'API.

Il test non tenta molto di progettare un'API. È stato impostato un sistema con stato in cui tutte le funzionalità sono contenute nella GameOfLifeclasse esterna .

Se dovessi scrivere questa applicazione, penserei invece ai pezzi che voglio costruire. Ad esempio, potrei creare una Cellclasse, scrivere test per questo, prima di passare all'applicazione più grande. Certamente creerei una classe per la struttura di dati "infinita in ogni direzione" necessaria per implementare correttamente Conway e testarla. Una volta fatto tutto ciò, avrei pensato di scrivere la classe generale che ha un mainmetodo e così via.

È facile sorvolare il passaggio "scrivere un test non riuscito". Ma scrivere il test fallito che funziona nel modo desiderato è il cuore di TDD.


1
Considerando una cella sarebbe solo un involucro per un boolean, quel design sarebbe sicuramente peggiore per le prestazioni. A meno che non debba essere estendibile in futuro ad altri automi cellulari con più di due stati?
user253751

2
@immibis Questo è il cavillo sui dettagli. È possibile iniziare con una classe che rappresenta la raccolta di celle. È anche possibile migrare / unire la classe di cella e i relativi test con una classe che rappresenta una raccolta di celle in un secondo momento se le prestazioni sono un problema.
Eric

@immibis Il numero di vicini live può essere memorizzato per motivi di performance. Numero di zecche che la cellula è stata viva, per motivi coloranti.
Blorgbeard esce il

L'ottimizzazione prematura di @immibis è il male ... Inoltre, evitare l'ossessione primitiva è il modo migliore per scrivere un codice di buona qualità, indipendentemente da quanti stati supporta. Dai un'occhiata a: jamesshore.com/Blog/PrimitiveObsession.html
Paul

0

C'è una diversa scuola di pensiero su questo.

Alcuni dicono: il test di non compilazione è un errore - vai a correggere scrivi il codice di produzione più piccolo disponibile.

Alcuni dicono: è OK scrivere prima il test per verificare se fa schifo (o no) formica quindi creare classi / metodi mancanti

Con il primo approccio sei davvero in un ciclo di refactor rosso-verde. Con il secondo hai una panoramica un po 'più ampia di ciò che vuoi ottenere.

Sta a te scegliere in che modo lavori. IMHO entrambi gli approcci sono validi.


0

Anche quando realizzo qualcosa in un modo "hacking together", continuo a pensare alle classi e ai passaggi che saranno coinvolti nell'intero programma. Quindi hai pensato a tutto questo e scritto prima queste prove di progettazione come test - è fantastico!

Ora continua a ripetere entrambe le implementazioni per completare questo test iniziale, quindi aggiungi altri test per migliorare ed estendere il design.

Ciò che potrebbe aiutarti è usare Cucumber o simili per scrivere i tuoi test.


0

Prima di iniziare a scrivere i test, dovresti pensare a come progettare il tuo sistema. Durante la fase di progettazione dovresti dedicare un certo tempo. Se lo hai fatto, non otterrai questa confusione sul TDD.

TDD è solo un collegamento di approccio allo sviluppo : TDD
1. Aggiungi un test
2. Esegui tutti i test e verifica se il nuovo fallisce
3. Scrivi un codice
4. Esegui test
5. Codice refactor
6. Ripeti

TDD ti aiuta a coprire tutte le funzionalità richieste che hai pianificato prima di iniziare a sviluppare il tuo software. link: vantaggi


0

Non mi piacciono i test a livello di sistema scritti in Java o C # per questo motivo. Guarda SpecFlow per c # o uno dei framework di test basati su Cucumber per java (forse JBehave). Quindi i tuoi test possono apparire più simili a questo.

inserisci qui la descrizione dell'immagine

E puoi cambiare il design degli oggetti senza dover cambiare tutti i test di sistema.

(I test unitari "normali" sono ottimi quando si eseguono test su singole classi.)

Quali sono le differenze tra i framework BDD per Java?

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.