Come posso iniziare a utilizzare TDD per codificare alcune semplici funzionalità?


9

Fondamentalmente ho l'essenza di TDD. Sono venduto che è utile e ho una ragionevole padronanza del framework MSTEST. Tuttavia, ad oggi non sono stato in grado di laurearmi per usarlo come metodo di sviluppo primario. Principalmente, lo uso come surrogato per scrivere app console come driver di test (il mio approccio tradizionale).

La cosa più utile al riguardo per me è il modo in cui assorbe il ruolo dei test di regressione.

Non ho ancora costruito nulla che isola specificamente vari comportamenti testabili, che è un'altra grande parte dell'immagine che conosco.

Quindi questa domanda è quella di chiedere indicazioni su quali potrebbero essere i primi test che potrei scrivere per la seguente attività di sviluppo: voglio produrre codice che incapsuli l'esecuzione dell'attività alla maniera del produttore / consumatore.

Mi sono fermato e ho deciso di scrivere questa domanda dopo aver scritto questo codice (chiedendomi se potevo effettivamente usare TDD davvero questa volta)

Codice:

interface ITask
{
    Guid TaskId { get; }
    bool IsComplete { get; }
    bool IsFailed { get; }
    bool IsRunning { get; }
}

interface ITaskContainer
{
    Guid AddTask(ICommand action);
}

interface ICommand
{
    string CommandName { get; }
    Dictionary<string, object> Parameters { get; }
    void Execute();
}

Avresti dovuto scrivere prima i test e poi le interfacce! L'idea è che TDD è per la tua API.

1
Come si possono scrivere test per interfacce che non esistono ancora? Non si compileranno nemmeno.
Robert Harvey,

5
Questo è il tuo primo test fallito.
cori,

Risposte:


10

A partire da questo concetto:
1) Inizia con il comportamento che desideri. Scrivi un test per questo. Vedi test fallito.
2) Scrivi abbastanza codice per far passare il test. Vedi tutti i test superati.
3) Cerca il codice ridondante / sciatto -> refactor. Vedi i test ancora superati. Vai a 1

Quindi su # 1, diciamo che vuoi creare un nuovo comando (sto estendendo a come funzionerebbe il comando, quindi abbi pazienza con me). (Inoltre, sarò un po 'pragmatico piuttosto che estremo TDD)

Il nuovo comando si chiama MakeMyLunch, quindi devi prima creare un test per istanziarlo e ottenere il nome del comando:

@Test
public void instantiateMakeMyLunch() {
   ICommand command = new MakeMyLunchCommand();
   assertEquals("makeMyLunch",command.getCommandName());
}

Questo fallisce, costringendoti a creare la nuova classe di comando e far sì che ritorni il suo nome (il purista direbbe che si tratta di due round di TDD, non 1). Quindi crei la classe e la fai implementare l'interfaccia ICommand, incluso restituire il nome del comando. L'esecuzione di tutti i test ora mostra tutti i passaggi, quindi si procede alla ricerca di opportunità di refactoring. Probabilmente nessuno.

Quindi, dopo, vuoi che venga eseguito. Quindi devi chiederti: come faccio a sapere che "MakeMyLunch" ha "preparato il mio pranzo" con successo. Cosa cambia nel sistema a causa di questa operazione? Posso fare un test per questo?

Supponiamo che sia facile testare per:

@Test
public void checkThatMakeMyLunchIsSuccessful() {
   ICommand command = new MakeMyLunchCommand();
   command.execute();
   assertTrue( Lunch.isReady() );
}

Altre volte, questo è più difficile e ciò che vuoi veramente fare è testare le responsabilità del soggetto sotto test (MakeMyLunchCommand). Forse la responsabilità di MakeMyLunchCommand è di interagire con Frigo e Microonde. Quindi per testarlo puoi usare un finto frigorifero e un finto microonde. [due framework simulati di esempio sono Mockito e nMock o guarda qui .]

Nel qual caso dovresti fare qualcosa come il seguente pseudo codice:

@Test
public void checkThatMakeMyLunchIsSuccessful() {
   Fridge mockFridge = mock(Fridge);
   Microwave mockMicrowave = mock(Microwave);
   ICommand command = new MakeMyLunchCommand( mockFridge, mockMicrowave );
   command.execute();
   mockFramework.assertCalled( mockFridge.removeFood );
   mockFramework.assertCalled( microwave.turnon );
}

Il purista dice di testare la responsabilità della tua classe - le sue interazioni con altre classi (il comando ha aperto il frigorifero e acceso il forno a microonde?).

Il pragmatico dice test per un gruppo di classi e test per il risultato (il tuo pranzo è pronto?).

Trova il giusto equilibrio adatto al tuo sistema.

(Nota: considera che forse sei arrivato alla struttura della tua interfaccia troppo presto. Forse puoi lasciarlo evolvere mentre scrivi i test e le implementazioni delle tue unità, e nel passaggio 3 "noti" l'opportunità di interfaccia comune).


se non avessi pre scritto la mia interfaccia, quale domanda avrebbe portato alla creazione del metodo Execute () - alcuni dei miei tentativi iniziali di TDD si sono bloccati quando non ho avuto un "passo" per stimolare funzionalità aggiuntive - ottengo la sensazione c'è un problema latente di gallina / uovo che deve essere
evitato

1
Buona domanda! Se l'unico comando che hai fatto è stato "MakeMyLunchCommand", il metodo potrebbe essere iniziato con ".makeMyLunch ()". Che sarebbe andato bene. Quindi si esegue un altro comando ("NapCommand.takeNap ()"). Ancora nessun problema con metodi diversi. Quindi inizi a usarlo nel tuo ecosistema, che è probabilmente dove sei costretto a generalizzare nell'interfaccia ICommand. In generale, spesso ritardate la generalizzazione fino all'ultimo momento responsabile, perché YAGNI [ en.wikipedia.org/wiki/You_ain't_gonna_need_it ] =) Altre volte, sai che ci arriverai, quindi inizi con esso.
jayraynet,

(Anche in questo caso si suppone che si stia utilizzando un IDE moderno che renda banali le operazioni di refactoring come i nomi dei metodi)
jayraynet,

1
grazie ancora per il consiglio, è una specie di pietra miliare per me vedere finalmente tutti i pezzi e come si adattano - e sì, il refactor rapido è nella mia cassetta degli attrezzi
Aaron Anodide
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.