Nel TDD, ho intenzione di testare insieme le unità quando ha senso. Per come la vedo io, uso mock / stub per due motivi: il comportamento del sistema diventa troppo complesso per testare efficacemente l'implementazione completa e la piena implementazione può fare cose che non voglio accadere.
Fondamentalmente, più oggetti sono coinvolti in un test, più difficile sarà il sistema da prevedere. Hai previsto il comportamento del sistema per scrivere il test. Alcuni oggetti possono avere un comportamento complesso e può essere difficile indurre casi di bordo particolari in una unità. Quindi può essere veramente utile per deridere quegli oggetti.
In altri casi, gli oggetti hanno effetti collaterali indesiderabili. Supponiamo che tu abbia un CreditCardProcessor. Non vuoi davvero caricare le carte di credito mentre i tuoi test sono in corso. Altri elementi come la grafica di disegno o l'accesso alle risorse web potrebbero essere nella stessa categoria.
Quando un oggetto ha una dipendenza, come decidi se includere l'oggetto reale o una specie di finto / stub?
In primo luogo, se c'è qualche possibilità che il comportamento dell'oggetto cambi durante lo sviluppo, lo spegnerei. Ad esempio, considera una classe di priorità di priorità rispetto a una classe di strategia di prezzo. Quasi certamente una coda di priorità manterrà sempre lo stesso comportamento. Tuttavia, è probabile che la strategia di determinazione dei prezzi cambi molto. Di conseguenza, non vuoi che altri test dipenda dal comportamento all'interno della strategia di prezzo. Se lo fanno, finirai per rompere gli altri test inutilmente. Tuttavia, non è davvero un grosso problema per la coda di priorità perché il comportamento non dovrebbe mai cambiare.
In secondo luogo, in che modo "grasso" è l'interfaccia tra gli oggetti? Se gli oggetti hanno un'interfaccia molto semplice, allora il mocking è facile e lo farò. Se gli oggetti hanno un'interfaccia complessa, il sbeffeggiamento è difficile e meno probabile che valga la pena. In questo caso, contrapponiamo un oggetto di connessione al database e una strategia di prezzo. L'interfaccia delle strategie di prezzo dovrebbe essere ragionevolmente semplice, si spera solo un metodo CalculatePrice (SalesOrderItem). Certo, il codice reale può fare ogni sorta di cose con SalesOrderItem, ma il tuo stub non deve occuparsene. D'altra parte, una connessione al database ha passato istruzioni SQL che gli danno un'interfaccia abbastanza complessa. Prendere in giro il database è davvero difficile perché hai controllato tutte le query che sono state fatte e fornito una risposta corretta. Inoltre, non stai verificando che le query siano valide (solo che corrispondono a quanto ti aspetti), in questi casi il controllo su un database reale ha più senso, in questo modo si verifica effettivamente che le query funzionino e i test passa ancora se riscrivi le query per dare gli stessi risultati ma in un modo diverso.
In terzo luogo, se un oggetto sarà lento lo spengo. Se si dispone di un database, le chiamate ad esso saranno abbastanza lenta che richiede l'uso di uno stub per evitare di dover effettuare una chiamata. Simile per l'accesso Web, ecc.
Ho appena usato i database come esempio di qualcosa che dovresti stub e anche non stub. Ho stub i miei database con un database in memoria sqlite che evita il problema delle prestazioni, ma consente comunque di testare il mio SQL. In realtà sto usando un framework che genera SQL specifico per il mio database per me, quindi è un lavoro.
Nel tuo caso attuale, dichiari:
The majority of our unit tests are behavioural tests which often became false negative (false red) during refactoring (just because some sequence of dependencies calls changed).
Come ho capito, i test falliscono perché prima veniva chiamato foo () e poi bar (). Ora bar () è chiamato then foo (). Se l'ordine di chiamare foo () e bar () non ha importanza, i tuoi test non dovrebbero controllare chi ha chiamato per primo. Il tuo test dovrebbe verificare solo che siano stati entrambi chiamati.