Difficoltà con TDD e Refactoring (O - Perché è più doloroso di quanto dovrebbe essere?)

20

Volevo insegnarmi a usare l'approccio TDD e avevo un progetto su cui volevo lavorare da un po '. Non era un progetto di grandi dimensioni, quindi ho pensato che sarebbe un buon candidato per TDD. Tuttavia, sento che qualcosa è andato storto. Lasciatemi fare un esempio:

Ad un livello elevato il mio progetto è un componente aggiuntivo per Microsoft OneNote che mi consentirà di monitorare e gestire i progetti più facilmente. Ora, volevo anche mantenere la logica di business per questo come disaccoppiata da OneNote il più possibile possibile nel caso in cui ho deciso di creare il mio storage personalizzato e il back end un giorno.

Per prima cosa ho iniziato con un semplice test di accettazione di parole semplici per delineare ciò che volevo fare per la mia prima caratteristica. Assomiglia a qualcosa del genere (abbreviato per brevità):

  1. L'utente fa clic su crea progetto
  2. Tipi di utente nel titolo del progetto
  3. Verifica che il progetto sia stato creato correttamente

Ignorando le informazioni sull'interfaccia utente e alcuni piani di intermediazione vengo al mio primo test di unità:

[TestMethod]
public void CreateProject_BasicParameters_ProjectIsValid()
{
    var testController = new Controller();
    Project newProject = testController(A.Dummy<String>());
    Assert.IsNotNull(newProject);
}

Fin qui tutto bene. Rosso, verde, refactoring, ecc. Va bene, adesso serve davvero risparmiare. Tagliando alcuni passaggi, finisco con questo.

[TestMethod]
public void CreateProject_BasicParameters_ProjectMatchesExpected()
{
    var fakeDataStore = A.Fake<IDataStore>();
    var testController = new Controller(fakeDataStore);
    String expectedTitle = fixture.Create<String>("Title");
    Project newProject = testController(expectedTitle);

    Assert.AreEqual(expectedTitle, newProject.Title);
}

Mi sento ancora bene a questo punto. Non ho ancora un data store concreto, ma ho creato l'interfaccia come avrei previsto.

Ho intenzione di saltare alcuni passaggi qui perché questo post sta diventando abbastanza lungo, ma ho seguito processi simili e alla fine ottengo questo test per il mio archivio dati:

[TestMethod]
public void SaveNewProject_BasicParameters_RequestsNewPage()
{
    /* snip init code */
    testDataStore.SaveNewProject(A.Dummy<IProject>());
    A.CallTo(() => oneNoteInterop.SavePage()).MustHaveHappened();
}

Questo è andato bene fino a quando ho provato a implementarlo:

public String SaveNewProject(IProject project)
{
    Page projectPage = oneNoteInterop.CreatePage(...);
}

E il problema è proprio dove si trova "...". Ora mi rendo conto che in questo punto CreatePage richiede un ID di sezione. Non me ne ero reso conto quando pensavo al livello del controller perché mi interessava solo testare i bit rilevanti per il controller. Tuttavia, fino a qui mi rendo conto che devo chiedere all'utente un luogo in cui archiviare il progetto. Ora devo aggiungere un ID di posizione all'archivio dati, quindi aggiungerne uno al progetto, quindi aggiungerne uno al controller e aggiungerlo a TUTTI i test già scritti per tutte queste cose. È diventato molto noioso molto rapidamente e non posso fare a meno di pensare che mi sarei accorto più rapidamente se avessi delineato il progetto in anticipo piuttosto che averlo progettato durante il processo TDD.

Qualcuno può spiegarmi se ho fatto qualcosa di sbagliato in questo processo? C'è comunque questo tipo di refactoring può essere evitato? O è comune? Se è comune ci sono modi per renderlo più indolore?

Grazie a tutti!

    
posta Landon 01.10.2013 - 03:13
fonte

3 risposte

19

Mentre TDD è (giustamente) pubblicizzato come un modo per progettare e far crescere il tuo software, è comunque una buona idea pensare in anticipo al design e all'architettura. IMO, "abbozzare il design in anticipo" è un gioco leale. Spesso, tuttavia, questo sarà a un livello più alto rispetto alle decisioni di progettazione a cui verrai indirizzato tramite TDD.

È anche vero che quando le cose cambiano, di solito devi aggiornare i test. Non c'è modo di eliminarlo completamente, ma ci sono alcune cose che puoi fare per rendere i tuoi test meno fragili e minimizzare il dolore.

  1. Per quanto possibile, tieni i dettagli di implementazione fuori dai test. Ciò significa testare solo tramite metodi pubblici e, ove possibile, favorire verifica basata sull'interazione basata sull'interazione . In altre parole, se provi il risultato di qualcosa invece dei passaggi per arrivarci, i tuoi test dovrebbero essere meno fragili.

  2. Riduci al minimo la duplicazione nel codice di prova, proprio come faresti nel codice di produzione. Questo post è un buon riferimento. Nel tuo esempio, sembra doloroso aggiungere la proprietà ID al tuo costruttore perché hai invocato il costruttore direttamente in diversi test diversi. Invece, prova ad estrarre la creazione dell'oggetto su un metodo o inizializzarlo una volta per ogni test in un metodo di inizializzazione del test.

risposta data 01.10.2013 - 05:46
fonte
10

...I can't help but feel like I would have caught this quicker if I sketched out the design ahead of time rather than letting it be designed during the TDD proces...

Forse, forse no

Da un lato, TDD ha funzionato bene, fornendoti test automatici durante la creazione delle funzionalità e interrompendoli immediatamente quando hai dovuto modificare l'interfaccia.

D'altra parte, forse se avessi iniziato con la funzione di alto livello (SaveProject) invece di una funzione di livello inferiore (CreateProject), avresti notato i parametri mancanti prima.

Quindi di nuovo, forse non avresti. È un esperimento irripetibile.

Ma se stai cercando una lezione per la prossima volta: inizia in alto. E pensa prima al design quanto vuoi.

    
risposta data 01.10.2013 - 07:41
fonte
0

link Da circa 2:22:00 alla fine (circa 1 ora). Spiacente che il video non sia gratuito, ma non ne ho trovato uno gratuito che lo spieghi così bene.

Una delle migliori presentazioni di scrittura di codice verificabile è in questa lezione. È una classe AngularJS, ma la parte di test è tutto intorno al codice java, principalmente perché ciò di cui parla non ha nulla a che fare con il linguaggio e tutto ciò che riguarda la scrittura di un buon codice testabile in primo luogo.

La magia sta scrivendo codice verificabile, piuttosto che scrivere test di codice. Non si tratta di scrivere codice che finge di essere un utente.

Passa anche del tempo a scrivere le specifiche sotto forma di asserzioni di test.

    
risposta data 03.10.2013 - 22:36
fonte

Leggi altre domande sui tag