To mock or not to mock
Summary: nice paper by Jeff Langr on the subject of mocks
Pubblicato oggi nella sezione file della lista http://groups.yahoo.com/group/testdrivendevelopment/
No perfect way to implement mocks exists. … This should suggest that while mocks can be useful in testing, they introduce other issues into a system … Mocking is still an extremely valuable tool. Slightly imperfect design is almost always the better choice over the inability to test. But the rule for mocking is still, don’t… unless you must.
Langr esprime alcune perplessità che ho sempre avuto sui mock, ma che non ero in grado di spiegare bene. Introdurre un mock comporta sempre una modifica al design del sistema, che non sarebbe stata introdotta se non per il testing. Io sono sempre dell’idea che vale la pena di introdurre modifiche nel codice, se sono necessarie per testarlo. Qualsiasi circuito integrato contiene della circuiteria che ha il solo scopo di testarne il funzionamento. Jeff discute le possibili alternative: passare l’interfaccia da mock-are nel costruttore, con alcune varianti, oppure la soluzione preferita, il deferred factory-based mocking..
Sono d’accordo con le conclusioni di Jeff:
- Meglio usare i veri collaboratori di una classe, se possibile, piuttosto che un mock.
- Non usare i mock come scusa per non sforzarsi di trovare un design meno complesso
- Preferire lo state-based testing al test basato sulle sequenze di chiamate attese; quest’ultimo rende difficile il refactoring
- meglio usare un mock fatto a mano, piuttosto che un jMock o simili
November 3rd, 2006 at 23:40
Secondo me qui non c’e’ un dogma assoluto, quindi hai ragione a preferire “la tua strada”. E’ possibile sviluppare un’applicazione sia testando solo dal punto di vista dello stato che dal punto di vista dei collaboratori.
Io attualmente mi trovo bene sviluppando top-down partendo da un’acceptance ed immergendo in componenti via via in contesti moccati, fino a raggiungere il confine del sistema, dove l’ultimo mock viene comunemente chiamato stub. Ho applicato piu’ o meno tutti i tricks che Jeff descrive nel suo paper ed ho anche affrontato la difficolta’ del refactoring. Ma per me e’ non e’ proprio fatica, fa parte del gioco. Mi spiego.
Che tu lo faccia esplicitamente o no, prima di scrivere codice fai mentalmente o con i tuoi collaboratori una sessione CRC dove emergono dei poli di responsabilita’ (ad es. Controller, il Formatter, l’Aggregator, il Parser ecc.) Questa costituisce una bozza, un filo conduttore che puo’ stabilizzarsi o prendere totalmente un’altra strada in quanto applico un design evolutivo. Cominciando dall’alto forzo il componente oggetto del test a comportarsi come vorrei, lo faccio parlare con i suoi presunti collaboratori e magari ne scopro di nuovi oppure scopro di averne troppi. Cosi’ la trama dei componenti cambia di continuo.
Io voglio che questo colloquio stia nei test, non lo voglio semplicemente su un schizzo UML. Voglio che se cambio idea sulla responsabilita’ di un componente e sul contratto che esso espone (che puo’ essere mappato su piu’ chiamate a metodo, che puo’ richiedere un certo ordine o diversi flussi in base alle risposte) io sia forzato a renderne conto ai test. Questo mi costringe a chiedermi perche’ ho cambiato idea in rapporto al precedente schema CRC, se ho sbagliato, cosa posso migliorare la prossima volta.
Se non uso i mock, ma piu’ in specifico, se non scolpisco nei test cosa mi aspetto che venga chiamato, quando e come, perdo questo feedback. Per questo quando arriva il momento del refactoring sono contento, perche’ c’e’ qualcosa da imparare. Se faccio TDD secco, verificando solo i valori, questo feedback lo perdo. D’accordo che e’ fatica in piu’, ma col tempo sono sempre piu’ veloce e soprattutto, le collaborazioni tra i miei oggetti sembrano sempre piu’ azzeccate.
Una cosa che mi sorprende nel paper di Jeff e’ il fatto che non citi quello che secondo me e’ il motivo piu’ importante per utilizzare i mock in questo modo, ovvero guidare il design. Ci sono anche altre grossolanita’: tu hai mai visto in un design OO dell’applicazione X un oggetto che si chiama XApplication? Se si’, e’ indice di mentalita’ procedurale. Secondo: quelle che lui chiama aumento delle dipendenze (proprio sull’oggetto PortfolioApplication che normalmente non esiste) io lo chiamo IOC: sviluppare via mock ti da gratis inversione di controllo. Credo siano ormai noti i benefici di questo approccio. E poi vabbe’, dappertutto si cita come negativo il fatto che l’approccio generi decisioni a livello di design quand’e’ esattamente quello a cui mi serve…
Grazie Matteo per l’ottimo spunto.
A presto
Renzo
November 4th, 2006 at 16:01
Ciao Renzo,
grazie a te per la risposta anche più articolata di quello che avevo scritto io… :)
Sì, io scrivo sempre con il tono di “si fa COSI’ e basta!” ma è certo che l’approccio giusto non è sempre lo stesso; dipende in parte dal tipo di applicazione ma soprattutto dal modo di ragionare, e anche dal livello di esperienza. Mi pare di capire che se io e te ci mettessimo a lavorare in paio, passeremmo 9/10 del tempo a discutere… sarebbe divertente! Ed è chiaro che hai un livello di esperienza e familiarità con l’O-O molto maggiore del mio.
Non credo che condividerei il tuo modo di lavorare, ma come tante altre cose forse occorre vedere il maestro in azione per capire bene come funziona. (Ci sono libri o articoli che descrivono uno stile simile al tuo?) Io penso fortemente state-based; per il mio modo di vedere imporre un ordine preciso nelle chiamate rappresenta un aumento dell’accoppiamento fra le classi, per cui cerco di evitare quando posso le dipendenze “temporali”. Se devo dialogare con un server smtp, le chiamate vanno fatte in un ordine preciso (prima il mittente, poi i destinatari); ma se lo dovessi progettare io, renderei indifferente l’ordine delle chiamate. Il fatto che i test non si rompano se cambio l’ordine delle chiamate lo vedo come un bonus :)
Tornando al caso di Jeff, credo che l’oggetto PortolioApplication fosse lì per rappresentare “il resto dell’applicazione” o meglio “un generico client di Portfolio”. Non credo che Jeff sia solito mettere un grosso Singleton al centro dei suoi design :)
A me piace questo articolo soprattutto perché spiega i pro e i contro di due o tre approcci a un problema comune (come introdurre un mock minimizzando gli impatti sul codice sotto test) in maniera molto semplice.