TDD: Deridere gli oggetti strettamente accoppiati

9

A volte gli oggetti devono solo essere strettamente accoppiati. Ad esempio, una classe CsvFile dovrà probabilmente lavorare strettamente con la classe CsvRecord (o l'interfaccia ICsvRecord ).

Tuttavia, da quanto ho appreso in passato, uno dei principi fondamentali dello sviluppo basato su test è "Non testare più di una classe alla volta". Significa che dovresti usare ICsvRecord mock o stub piuttosto che istanze effettive di CsvRecord .

Tuttavia, dopo aver provato questo approccio, ho notato che prendere in giro la classe CsvRecord può diventare un po 'peloso. Il che mi porta a una delle due conclusioni:

  1. È difficile scrivere test unitari! Questo è un odore di codice! Refactoring!
  2. Prendere in giro ogni singola dipendenza è irragionevole.

Quando ho sostituito i miei mock con istanze CsvRecord effettive, le cose sono andate molto più agevolmente. Guardando in giro per i pensieri di altre persone mi sono imbattuto in questo post del blog , che sembra supportare il n. 2 sopra. Per gli oggetti che sono naturalmente strettamente accoppiati, non dovremmo preoccuparci così tanto di prendere in giro.

Sto andando fuori pista? Ci sono degli svantaggi dell'assunzione n. 2 sopra? Dovrei davvero pensare al refactoring del mio design?

    
posta Phil 11.08.2012 - 00:57
fonte

5 risposte

11

Se hai davvero bisogno di un coordinamento tra queste due classi, scrivi una classe CsvCoordinator che incapsula le tue due classi e testala.

Tuttavia, contesta l'idea che CsvRecord non sia testabile indipendentemente. CsvRecord è fondamentalmente una classe DTO , non è così? È solo una raccolta di campi, con forse un paio di metodi di supporto. E CsvRecord può essere utilizzato in altri contesti oltre a CsvFile ; per esempio puoi avere una collezione o una matrice di CsvRecord s.

Prova CsvRecord prima. Assicurati di superare tutti i test. Quindi, vai avanti e usa CsvRecord con la tua classe CsvFile durante il test. Usalo come uno stub / mock pre-testato; riempilo con i dati di test rilevanti, passalo a CsvFile e scrivi i casi di test con questo.

    
risposta data 11.08.2012 - 01:19
fonte
5

Il motivo per testare una classe alla volta è che non si desidera che i test di una classe abbiano dipendenze sul comportamento di una seconda classe. Ciò significa che se il tuo test per la Classe A esercita una qualsiasi delle funzionalità della Classe B, allora dovresti prendere in giro la Classe B per rimuovere la dipendenza da particolari funzionalità all'interno della Classe B.

Una classe come CsvRecord mi sembra che sia principalmente per la memorizzazione dei dati - non è una classe con troppe funzionalità a sé stante. Cioè, potrebbe avere costruttori, getter, setter, ma nessun metodo con una reale logica sostanziale. Naturalmente, sto indovinando qui - forse hai scritto una classe chiamata CsvRecord che fa numerosi calcoli complessi.

Ma se CsvRecord non ha una propria logica, non c'è niente da guadagnare deridendolo. Questa è solo la vecchia massima - "non prendere in giro oggetti di valore" .

Quindi, quando si prende in considerazione l'ipotesi di prendere in giro una particolare classe (per un test di una classe diversa), si dovrebbe tener conto di quanto della sua stessa logica abbia quella classe, e quanta parte di tale logica verrà eseguita nel corso di il tuo test.

    
risposta data 11.08.2012 - 10:46
fonte
1

No. # 2 va bene. Le cose possono essere, e dovrebbe essere strettamente accoppiato se i loro concetti sono strettamente accoppiati. Questo dovrebbe essere raro e generalmente evitato, ma nell'esempio che hai fornito ha senso.

    
risposta data 11.08.2012 - 01:20
fonte
0

Le classi "accoppiate" sono reciprocamente dipendenti l'una dall'altra. Questo non dovrebbe essere il caso in quello che stai descrivendo - un CsvRecord non dovrebbe preoccuparsi del CsvFile che lo contiene, quindi la dipendenza va solo in un modo. Va bene, ed è non accoppiamento stretto.

Dopotutto, se una classe contiene la variabile Nome stringa, non dichiareresti che è strettamente accoppiata a String, vero?

Quindi, unit test il CsvRecord per il suo comportamento desiderato.

Quindi usa un framework di simulazione (Mockito è fantastico) per verificare se la tua unità sta interagendo con gli oggetti che dipende correttamente. Il comportamento che si desidera testare, in realtà, è che CsvFile gestisce CsvRcords nel modo previsto. Il funzionamento interno di CvsRecord non dovrebbe avere importanza - è come CvsFile comunica con esso.

Infine, TDD non è solo sui test unitari. È certamente possibile (e dovrebbe) iniziare con test funzionali che esaminano il comportamento funzionale di come funzionano i componenti più grandi, ovvero la storia o lo scenario dell'utente. I test delle tue unità stabiliscono le aspettative e verificano i pezzi, i test funzionali fanno lo stesso per il tutto.

    
risposta data 11.08.2012 - 06:15
fonte
0

Ci sono davvero due domande qui. Il primo è se esistono situazioni in cui non è consigliabile prendere in giro un oggetto. È indubbiamente vero, come dimostrano le altre eccellenti risposte. La seconda domanda è se il tuo caso particolare è una di quelle situazioni. Su questa domanda non sono convinto.

Probabilmente il motivo più comune per non prendere in giro una classe è se si tratta di una classe di valore. Tuttavia, devi guardare il motivo dietro la regola. Non è perché la classe derisa sarà cattiva in qualche modo, è perché sarà essenzialmente identica all'originale. In questo caso, il test dell'unità non sarebbe più semplice utilizzando la classe originale.

È molto probabile che il codice sia una delle rare eccezioni in cui il refactoring non sarebbe d'aiuto, ma dovresti dichiararlo tale solo dopo che gli sforzi di refactoring diligente non hanno funzionato. Anche gli sviluppatori esperti possono avere difficoltà a vedere alternative al proprio design. Se non riesci a pensare ad alcun modo per migliorarlo, chiedi a qualcuno con esperienza di dargli una seconda occhiata.

La maggior parte delle persone sembra presupporre che il tuo CsvRecord sia una classe di valore. Prova a realizzarlo. Rendilo immutabile se puoi. Se hai due oggetti con i puntatori, rimuovi uno di essi e scopri come farlo funzionare. Cerca luoghi in cui dividere classi e funzioni. Il posto migliore per suddividere una classe potrebbe non sempre coincidere con il layout fisico del file. Prova a invertire la relazione genitore / figlio delle classi. Forse hai bisogno di una classe separata per leggere e scrivere file CSV. Forse hai bisogno di classi separate per gestire l'I / O del file e l'interfaccia per i livelli superiori. Ci sono molte cose da provare prima di dichiararlo irriconoscibile.

    
risposta data 12.08.2012 - 07:02
fonte

Leggi altre domande sui tag