Come ottenere correttamente l'API iniziale usando TDD?


12

Questa potrebbe essere una domanda piuttosto stupida dato che sono ai miei primi tentativi di TDD. Ho adorato il senso di fiducia che porta e la struttura generalmente migliore del mio codice, ma quando ho iniziato ad applicarlo su qualcosa di più grande degli esempi di giocattoli di una classe, ho incontrato difficoltà.

Supponiamo che tu stia scrivendo una specie di libreria. Sai cosa deve fare, conosci un modo generale di come dovrebbe essere implementato (per quanto riguarda l'architettura), ma continui a "scoprire" che è necessario apportare modifiche all'API pubblica durante la codifica. Forse devi trasformare questo metodo privato in un modello di strategia (e ora devi passare una strategia derisa nei tuoi test), forse hai smarrito una responsabilità qua e là e diviso una classe esistente.

Quando stai migliorando il codice esistente, TDD sembra davvero una buona scelta, ma quando scrivi tutto da zero, l'API per cui scrivi i test è un po '"sfocata" a meno che tu non faccia un grande progetto in anticipo. Cosa fai quando hai già 30 test sul metodo che ha cambiato la sua firma (e per quella parte, il comportamento)? Ci sono molti test da cambiare una volta sommati.


3
30 test su un metodo? Sembra che quel metodo abbia troppa complessità, o stai scrivendo troppi test.
Minthos,

Beh, potrei aver esagerato un po 'per esprimere il mio punto. Dopo aver controllato il codice generalmente ho meno di 10 metodi per test, molti dei quali sono inferiori a 5. Ma l'intera parte "tornare indietro e cambiarli a mano" è stata abbastanza frustrante.
Vytautas Mackonis,

6
@Minthos: riesco a pensare a 6 test dalla parte superiore della mia testa che qualsiasi metodo che prende una stringa spesso fallisce o si comporta male su una prima bozza di scrittura (null, vuoto, troppo lungo, non localizzato correttamente, scar ridimensionamento perf) . Allo stesso modo per i metodi che prendono una collezione. Per un metodo non banale, 30 sembra grande, ma non troppo irrealistico.
Steven Evers,

Risposte:


13

Quello che chiami "grande design in primo piano" Io chiamo "pianificazione ragionevole della tua architettura di classe".

Non è possibile far crescere un'architettura dai test unitari. Anche lo zio Bob lo dice.

Se non stai pensando attraverso l'architettura, se invece stai ignorando l'architettura e mettendo insieme i test e facendoli superare, stai distruggendo la cosa che permetterà all'edificio di rimanere in piedi perché è la concentrazione sul struttura del sistema e solide decisioni di progettazione che hanno aiutato il sistema a mantenere la sua integrità strutturale.

http://s3.amazonaws.com/hanselminutes/hanselminutes_0171.pdf , Pagina 4

Penso che sarebbe più sensato affrontare il TDD dal punto di vista della convalida del progetto strutturale. Come fai a sapere se il design non è corretto se non lo collaudi? E come si verifica che le modifiche siano corrette senza modificare anche i test originali?

Il software è "morbido" proprio perché è soggetto a modifiche. Se non ti senti a tuo agio sulla quantità di modifiche, continua a fare esperienza nella progettazione architettonica e il numero di modifiche che dovrai apportare alle architetture delle tue applicazioni diminuirà nel tempo.


Il fatto è che, anche con una "pianificazione ragionevole", ci si aspetta che cambi molto. Di solito lascio intatto circa l'80% della mia architettura iniziale con alcuni cambiamenti nel mezzo. Quel 20% è ciò che mi dà fastidio.
Vytautas Mackonis,

2
Penso che sia solo la natura dello sviluppo del software. Non puoi aspettarti di ottenere l'intera architettura al primo tentativo.
Robert Harvey,

2
+1 e non è contrario a TDD. TDD inizia quando si inizia a scrivere codice, proprio quando termina la progettazione. TDD può aiutarti a vedere cosa ti sei perso nel tuo design, permettendoti di riformattare il design e l'implementazione e continuare.
Steven Evers,

2
In realtà secondo Bob (e sono d'accordo con lui con tutto il cuore) anche scrivere codice è design. Avere un'architettura di alto livello è sicuramente necessario, ma il design non finisce quando si scrive il codice.
Michael Brown,

Davvero una buona risposta che colpisce l'unghia sulla testa. Vedo così tante persone, sia a favore che contro TDD, che sembrano "non progettare in grande stile" come "non progettare affatto, solo codificare" quando in realtà è uno sciopero contro le insane fasi di progettazione della cascata. Il design è sempre un buon investimento di tempo ed è cruciale per il successo di qualsiasi progetto non banale.
Sara,

3

Se fai TDD. Non è possibile modificare la firma e il comportamento senza averlo guidato dai test. Quindi i 30 test che hanno fallito sono stati eliminati nel processo o modificati / refactored insieme al codice. O ora sono obsoleti, sicuri da eliminare.

Non puoi ignorare il rosso 30 volte nel tuo ciclo di rifattore rosso-verde?

I test devono essere sottoposti a refactoring insieme al codice di produzione. Se te lo puoi permettere, riesegui tutti i test dopo ogni modifica.

Non abbiate paura di eliminare i test TDD. Alcuni test finiscono per testare i mattoni per ottenere il risultato desiderato. Il risultato desiderato a livello funzionale è ciò che conta. Test su passaggi intermedi dell'algoritmo che hai scelto / inventato possono o non possono avere molto valore quando c'è più di un modo per raggiungere il risultato o quando inizialmente eri in un vicolo cieco.

A volte è possibile creare alcuni test di integrazione decenti, conservarli ed eliminare il resto. Dipende in qualche modo dal fatto che tu lavori al rovescio o dall'alto in basso e quanto grandi passi fai.


1

Come ha appena detto Robert Harvey, probabilmente stai cercando di utilizzare TDD per qualcosa che dovrebbe essere gestito da un diverso strumento concettuale (ovvero: "design" o "modellazione").

Prova a progettare (o "modellare") il tuo sistema in un modo abbastanza astratto ("generale", "vago"). Ad esempio, se devi modellare un'auto, basta avere una classe di auto con un metodo e un campo vaghi, come startEngine () e i posti int. Cioè: descrivi ciò che vuoi esporre al pubblico , non come lo vuoi implementare. Prova ad esporre solo le funzionalità di base (lettura, scrittura, avvio, arresto, ecc.) E lascia che il codice client sia elaborato su di esso (preparMyScene (), killTheEnemy (), ecc.).

Scrivi i tuoi test assumendo questa semplice interfaccia pubblica.

Cambia il comportamento interno delle tue classi e metodi ogni volta che ne hai bisogno.

Se e quando è necessario modificare l'interfaccia pubblica e la suite di test, fermarsi e pensare. Molto probabilmente questo è un segno che c'è qualcosa di sbagliato nella tua API e nella tua progettazione / modellazione.

Non è insolito cambiare un'API. La maggior parte dei sistemi nella loro versione 1.0 mette esplicitamente in guardia i programmatori / utenti da possibili cambiamenti nella loro API. Nonostante ciò, un flusso continuo e incontrollato di modifiche alle API è un chiaro segno di cattiva progettazione (o totalmente mancante).

A proposito: di solito dovresti avere solo una manciata di test per metodo. Un metodo, per definizione, dovrebbe implementare una "azione" chiaramente definita su alcuni tipi di dati. In un mondo perfetto, questa dovrebbe essere una singola azione che corrisponde a un singolo test. Nel mondo reale non è insolito (e non sbagliato) avere poche "versioni" diverse della stessa azione e pochi test corrispondenti differenti. Di sicuro, dovresti evitare di fare 30 test con lo stesso metodo. Questo è un chiaro segno che il metodo cerca di fare troppo (e il suo codice interno è fuori controllo).


0

Lo guardo dal punto di vista dell'utente. Ad esempio, se le tue API mi consentono di creare un oggetto Person con nome ed età, è meglio che esistano metodi di costruzione Person (nome stringa, int età) e accessori per nome ed età. È semplice creare casi di test per nuove persone con e senza nome ed età.

Doug

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.