Iniezione di dipendenza: tartarughe fino in fondo?

Quindi mi chiedo come funziona il test unitario riguardo alle dipendenze esterne. Qui e altrove ho acquisito familiarità con l’iniezione di dipendenza e in che modo questo ci consente di testare un’unità (A) di codice. Tuttavia, sono confuso su come testare altre unità (B e C) che ora possiedono la dipendenza esterna in modo da poterle iniettare nell’unità originale (A).

Ad esempio, supponiamo che una class Foo usi una dipendenza esterna …

class Foo { private ExternalDependency ed; public int doSomethingWithExternalDependency() {...} } 

E la class Bar fa uso di Foo

 class Bar { public int doSomethingWithFoo { Foo f = new Foo(); int x = f.doSomethingWithExternalDependency(); // Do some more stuff ... return result; } } 

Ora, so che posso usare l’iniezione di dipendenza in modo da poter testare Foo , ma come faccio a testare la barra ? Immagino, posso, ancora una volta, usare l’iniezione di dipendenza, ma ad un certo punto alcune unità devono effettivamente creare la dipendenza esterna; quindi, come posso testare quell’unità?

Gli esempi forniti non utilizzano Iniezione delle dipendenze. Invece, Bar dovrebbe usare Constructor Injection per ottenere un’istanza di Foo, ma non ha senso inserire una class concreta . Invece, dovresti estrarre un’interfaccia da Foo (chiamiamola IFoo) e inserirla in Bar:

 public class Bar { private IFoo f; public Bar(IFoo f) { this.f = f; } public int doSomethingWithFoo { int x = this.f.doSomethingWithExternalDependency(); // Do some more stuff ... return result; } } 

Ciò consente di separare sempre consumatori e dipendenze .

Sì, ci sarà ancora un posto in cui è necessario comporre l’intero grafo dell’object dell’applicazione. Chiamiamo questo posto la Radice di composizione . È un componente dell’infrastruttura dell’applicazione , quindi non è necessario testare l’unità.

Nella maggior parte dei casi, dovresti prendere in considerazione l’utilizzo di un contenitore DI per quella parte e quindi applicare il modello Registra risoluzione di rilascio .

Tieni presente la differenza tra test delle unità e test di integrazione. Nel primo caso, si deriverebbe la dipendenza per cui fornisce un comportamento previsto allo scopo di testare la class che consuma la dipendenza. Nel secondo caso, un’istanza reale della dipendenza viene inizializzata per vedere se l’intera cosa funziona end-to-end.

Per utilizzare Iniezione delle dipendenze, le classi avranno le loro dipendenze iniettate in essi (diversi modi per farlo – iniezione del costruttore, iniezione di proprietà) e non istanziano loro stessi, come si fa nei propri esempi.

Inoltre, si estrarrebbe l’interfaccia di ciascuna dipendenza per aiutare con la testabilità e utilizzare l’interfaccia invece di un tipo di implementazione come dipendenza.

 class Foo { private IExternalDependency ed; public int doSomethingWithExternalDependency() {...} public Foo(IExternalDependency extdep) { ed = extdep; } } 

Ciò che la maggior parte delle persone fa è usare un sistema di derisione per deridere le dipendenze durante i test.

Puoi prendere in giro qualsiasi object a cui dipende la class sottoposta a test (compresi comportamento e valori di ritorno) – passa i mock alla class come dipendenze.

Ciò consente di testare la class senza fare affidamento sul comportamento delle sue dipendenze (implementate).

In alcuni casi, potresti voler usare falsi o stub anziché una struttura di derisione. Vedi questo articolo di Martin Fowler sulle differenze.


Per quanto riguarda il recupero di tutte le dipendenze, si utilizza un container IoC . Questo è un registro di tutte le dipendenze nel tuo sistema e comprende come istanziare ogni class con le sue dipendenze.

Quando collaudi un’unità in una class, dovresti prendere in giro le sue dipendenze, per testare la class in isolamento – questo indipendentemente dall’iniezione di dipendenza.

La risposta alla tua domanda su Bar è: sì, dovresti iniettare Foo. Una volta che si scende lungo il percorso DI, lo si utilizzerà attraverso l’intero stack. Se hai davvero bisogno di un nuovo Foo per ogni chiamata doSomethingWithFoo, potresti voler iniettare un FooFactory (che puoi poi prendere in giro a scopo di test), se vuoi che una singola Barra usi molti Foos.

Vorrei sottolineare che in caso di test unitario dovresti avere due set separati di test: uno per Foo.doSomethingWithExternalDependency e un altro per Bar.doSomethingWithFoo. Nell’ultimo set create un’implementazione fittizia di Foo e testate solo doSomethingWithFoo supponendo che doSomethingWithExternalDependency funzioni correttamente. Test di doSomethingWithExternalDependency in un set di test separato.