Critiche e svantaggi dell'iniezione di dipendenza


118

L'iniezione di dipendenza (DI) è un modello ben noto e alla moda. La maggior parte degli ingegneri ne conosce i vantaggi, come:

  • Rendere possibile / semplice l'isolamento nei test unitari
  • Definire esplicitamente le dipendenze di una classe
  • Facilitare una buona progettazione ( ad esempio principio della responsabilità singola (SRP))
  • Abilitazione rapida delle implementazioni di commutazione ( DbLoggeranziché ConsoleLoggerad esempio)

Ritengo che ci sia un ampio consenso del settore sul fatto che DI sia un modello buono e utile. Non ci sono troppe critiche al momento. Gli svantaggi menzionati nella comunità sono generalmente minori. Alcuni di quelli:

  • Aumento del numero di classi
  • Creazione di interfacce non necessarie

Attualmente discutiamo di architettura con il mio collega. È abbastanza conservatore, ma di mentalità aperta. Gli piace mettere in discussione le cose, che considero buone, perché molte persone nell'IT copiano solo la tendenza più recente, ripetono i vantaggi e in generale non pensano troppo - non analizzano troppo in profondità.

Le cose che vorrei chiedere sono:

  • Dovremmo usare l'iniezione delle dipendenze quando abbiamo una sola implementazione?
  • Dovremmo vietare la creazione di nuovi oggetti tranne quelli di linguaggio / framework?
  • L'iniezione di una singola implementazione è una cattiva idea (supponiamo di avere una sola implementazione, quindi non vogliamo creare un'interfaccia "vuota") se non prevediamo di testare un'unità di una determinata classe?

33
Stai davvero chiedendo l'iniezione di depenenza come modello o stai chiedendo di usare i framework DI? Queste sono cose davvero distinte, dovresti chiarire quale parte del problema ti interessa o chiedere esplicitamente su entrambi.
Frax,

10
@Frax sul pattern, non sui framework
Landeeyo,

10
Stai confondendo l'inversione di dipendenza con l'iniezione di dipendenza. Il primo è un principio di progettazione. Quest'ultima è una tecnica (di solito implementata con uno strumento esistente) per costruire gerarchie di oggetti.
jpmc26

3
Scrivo spesso test usando il database reale e nessun oggetto finto. Funziona davvero bene in molti casi. E quindi non hai bisogno di interfacce per la maggior parte del tempo. Se hai una UserServiceclasse, è solo un detentore della logica. Viene iniettata una connessione al database e i test vengono eseguiti all'interno di una transazione che viene ripristinata. Molti chiamerebbero questa cattiva pratica ma ho scoperto che funziona molto bene. Non è necessario contorcere il codice solo per i test e si ottiene il potere di ricerca dei bug dei test di integrazione.
usr

3
Il DI è quasi sempre buono. La cosa brutta è che molte persone pensano di conoscere DI ma sanno solo come usare un framework strano senza nemmeno essere sicuri di ciò che stanno facendo. Oggi la DI soffre molto della programmazione di culto del carico.
T. Sar,

Risposte:


160

Innanzitutto, vorrei separare l'approccio progettuale dal concetto di framework. L'iniezione di dipendenza al suo livello più semplice e fondamentale è semplicemente:

Un oggetto padre fornisce tutte le dipendenze richieste all'oggetto figlio.

Questo è tutto. Si noti che nulla in ciò richiede interfacce, framework, qualsiasi stile di iniezione, ecc. Per essere onesti, ho imparato a conoscere questo schema 20 anni fa. Non è nuovo

A causa di più di 2 persone che hanno confusione sul termine genitore e figlio, nel contesto dell'iniezione di dipendenza:

  • Il genitore è l'oggetto che crea un'istanza e configura l'oggetto figlio che utilizza
  • Il bambino è il componente progettato per un'istanza passiva. Vale a dire che è progettato per utilizzare le dipendenze fornite dal genitore e non crea istanze delle proprie dipendenze.

L'iniezione di dipendenza è un modello per la composizione degli oggetti .

Perché interfacce

Le interfacce sono un contratto. Esistono per limitare quanto possono essere strettamente accoppiati due oggetti. Non tutte le dipendenze hanno bisogno di un'interfaccia, ma aiutano a scrivere codice modulare.

Quando si aggiunge il concetto di unit test, è possibile avere due implementazioni concettuali per una determinata interfaccia: l'oggetto reale che si desidera utilizzare nella propria applicazione e l'oggetto deriso o stub che si utilizza per testare il codice che dipende dall'oggetto. Solo questo può essere una giustificazione sufficiente per l'interfaccia.

Perché i framework?

Essenzialmente l'inizializzazione e la fornitura di dipendenze per gli oggetti figlio può essere scoraggiante quando ce ne sono molti. I frame offrono i seguenti vantaggi:

  • Autowiring dipendenze ai componenti
  • Configurazione dei componenti con impostazioni di qualche tipo
  • Automatizzare il codice della piastra della caldaia in modo da non doverlo vedere scritto in più posizioni.

Hanno anche i seguenti svantaggi:

  • L'oggetto genitore è un "contenitore" e non nulla nel codice
  • Rende il test più complicato se non è possibile fornire le dipendenze direttamente nel codice di test
  • Può rallentare l'inizializzazione poiché risolve tutte le dipendenze usando la riflessione e molti altri trucchi
  • Il debug del runtime può essere più difficile, in particolare se il contenitore inietta un proxy tra l'interfaccia e il componente reale che implementa l'interfaccia (viene in mente la programmazione orientata all'aspetto integrata in Spring). Il contenitore è una scatola nera e non sono sempre costruiti con alcun concetto di facilitare il processo di debug.

Detto questo, ci sono dei compromessi. Per i piccoli progetti in cui non ci sono molte parti in movimento e ci sono poche ragioni per usare un framework DI. Tuttavia, per progetti più complicati in cui vi sono già alcuni componenti già realizzati per te, il framework può essere giustificato.

Che dire di [articolo casuale su Internet]?

Che ne pensi? Molte volte le persone possono diventare troppo zelanti e aggiungere un sacco di restrizioni e rimproverarti se non stai facendo le cose "in un unico modo". Non esiste un vero modo. Vedi se riesci a estrarre qualcosa di utile dall'articolo e ignorare le cose con cui non sei d'accordo.

In breve, pensa a te stesso e prova le cose.

Lavorare con "vecchie teste"

Impara più che puoi. Quello che troverai con molti sviluppatori che stanno lavorando nei loro anni '70 è che hanno imparato a non essere dogmatici su molte cose. Hanno metodi con cui hanno lavorato per decenni che producono risultati corretti.

Ho avuto il privilegio di lavorare con alcuni di questi e possono fornire un feedback brutalmente onesto che ha molto senso. E dove vedono valore, aggiungono quegli strumenti al loro repertorio.


6
@CarlLeth, ho lavorato con numerosi framework dalla Spring alle varianti .net. Spring ti consentirà di iniettare implementazioni direttamente nei campi privati ​​usando un po 'di magia nera di riflessione / classloader. L'unico modo per testare componenti costruiti in questo modo è utilizzare il contenitore. Spring ha corridori JUnit per configurare l'ambiente di test, ma è più complicato che configurare le cose da soli. Quindi sì, ho appena dato un esempio pratico .
Berin Loritsch,

17
C'è un altro svantaggio che trovo un ostacolo con DI attraverso i framework quando indosso il mio cappello per la risoluzione dei problemi / manutentore: l'azione spettrale a distanza che forniscono rende più difficile il debug offline. Nel peggiore dei casi, devo eseguire il codice per vedere come vengono inizializzate e passate le dipendenze. Ne parli nel contesto di "testing", ma in realtà è molto peggio se stai iniziando a guardare la fonte, mai mente cerca di farlo funzionare (il che può comportare una tonnellata di installazione). Impedire alla mia capacità di dire cosa fa il codice semplicemente dandogli un'occhiata è una cosa negativa.
Jeroen Mostert,

1
Le interfacce non sono contratti, sono semplicemente API. I contratti implicano la semantica. Questa risposta utilizza una terminologia specifica del linguaggio e convenzioni specifiche Java / C #.
Frank Hileman,

2
@BerinLoritsch Il punto principale della tua risposta è che il principio DI! = Qualsiasi dato quadro DI. Il fatto che Spring possa fare cose orribili e imperdonabili è uno svantaggio di Spring, non dei framework DI in generale. Un buon framework DI ti aiuta a seguire il principio DI senza brutti scherzi.
Carl Leth,

1
@CarlLeth: tutti i framework DI sono progettati per rimuovere o automatizzare alcune cose che il programmatore non desidera precisare, ma variano solo nel modo. Per quanto ne sappia, rimuovono tutti la capacità di sapere come (o se ) le classi A e B interagiscono semplicemente guardando A e B - per lo meno, devi anche guardare a DI setup / configuration / convegni. Non è un problema per il programmatore (è esattamente quello che vogliono, in realtà), ma un potenziale problema per un manutentore / debugger (forse lo stesso programmatore, in seguito). Questo è un compromesso che fai anche se il tuo DI framework è "perfetto".
Jeroen Mostert,

88

L'iniezione di dipendenza è, come la maggior parte dei modelli, una soluzione ai problemi . Quindi inizia chiedendo se hai anche il problema in primo luogo. In caso contrario, l'uso del modello molto probabilmente peggiorerà il codice .

Considerare innanzitutto se è possibile ridurre o eliminare le dipendenze. A parità di altre condizioni, vogliamo che ogni componente di un sistema abbia il minor numero possibile di dipendenze. E se le dipendenze sono sparite, la questione dell'iniezione o no diventa discutibile!

Si consideri un modulo che scarica alcuni dati da un servizio esterno, lo analizza, esegue alcune analisi complesse e scrive i risultati in un file.

Ora, se la dipendenza dal servizio esterno è hardcoded, sarà davvero difficile testare l'unità sull'elaborazione interna di questo modulo. Quindi potresti decidere di iniettare il servizio esterno e il file system come dipendenze dell'interfaccia, il che ti permetterà di iniettare mock invece che a sua volta rende possibile il test unitario della logica interna.

Ma una soluzione molto migliore consiste semplicemente nel separare l'analisi dall'input / output. Se l'analisi viene estratta in un modulo senza effetti collaterali, sarà molto più facile testarlo. Nota che il deridere è un odore di codice - non è sempre evitabile, ma in generale, è meglio se puoi provare senza fare affidamento sul deridere. Quindi, eliminando le dipendenze, si evitano i problemi che DI dovrebbe alleviare. Si noti che un tale design aderisce molto meglio a SRP.

Voglio sottolineare che DI non facilita necessariamente SRP o altri buoni principi di progettazione come separazione delle preoccupazioni, elevata coesione / basso accoppiamento e così via. Potrebbe anche avere l'effetto opposto. Considera una classe A che utilizza un'altra classe B internamente. B è usato solo da A e quindi completamente incapsulato e può essere considerato un dettaglio di implementazione. Se lo cambi per iniettare B nel costruttore di A, hai esposto questi dettagli di implementazione e ora la conoscenza di questa dipendenza e di come inizializzare B, la durata di B e così via devono esistere in qualche altra parte del sistema separatamente da A. Quindi hai un'architettura complessivamente peggiore con preoccupazioni che perdono.

D'altra parte ci sono alcuni casi in cui DI è davvero utile. Ad esempio per servizi globali con effetti collaterali come un logger.

Il problema è quando i modelli e le architetture diventano obiettivi in ​​se stessi piuttosto che strumenti. Sto solo chiedendo "Dovremmo usare DI?" è una specie di mettere il carro davanti al cavallo. Dovresti chiedere: "Abbiamo un problema?" e "Qual è la soluzione migliore per questo problema?"

Una parte della tua domanda si riduce a: "Dovremmo creare interfacce superflue per soddisfare le esigenze del modello?" Probabilmente ti rendi già conto della risposta a questo - assolutamente no ! Chiunque ti dica altrimenti sta cercando di venderti qualcosa - molto probabilmente costose ore di consulenza. Un'interfaccia ha valore solo se rappresenta un'astrazione. Un'interfaccia che imita semplicemente la superficie di una singola classe è chiamata "interfaccia di intestazione" e questo è un modello noto.


15
Non potrei essere più d'accordo! Nota anche che prendere in giro le cose per il gusto di farlo significa che non stiamo effettivamente testando le implementazioni reali. Se Autilizza Bin produzione, ma è stato testato solo contro MockB, i nostri test non ci dicono se funzionerà in produzione. Quando i componenti puri (senza effetti collaterali) di un modello di dominio si iniettano e si deridono a vicenda, il risultato è un enorme spreco di tempo per tutti, una base di codice gonfia e fragile e una scarsa fiducia nel sistema risultante. Deridere ai confini del sistema, non tra pezzi arbitrari dello stesso sistema.
Warbo,

17
@CarlLeth Perché pensi che DI renda il codice "testabile e gestibile" e il codice senza di esso meno? JacquesB ha ragione sul fatto che gli effetti collaterali sono ciò che danneggia la testabilità / manutenibilità. Se il codice non ha effetti collaterali, non ci interessa cosa / dove / quando / come chiama un altro codice; possiamo mantenerlo semplice e diretto. Se il codice non ha effetti collaterali che hanno per la cura. DI può estrarre gli effetti collaterali dalle funzioni e metterlo nei parametri, rendendo tali funzioni più testabili ma il programma più complesso. A volte è inevitabile (ad es. Accesso al DB). Se il codice non ha effetti collaterali, DI è solo una complessità inutile.
Warbo,

13
@CarlLeth: DI è una soluzione al problema di rendere il codice testabile da solo se ha dipendenze che lo vietano. Ma non riduce la complessità generale, né rende il codice più leggibile, il che significa che non aumenta necessariamente la manutenibilità. Tuttavia, se tutte queste dipendenze possono essere eliminate da una migliore separazione delle preoccupazioni, ciò "annulla perfettamente" i benefici di DI, poiché annulla la necessità di DI. Questa è spesso una soluzione migliore per rendere il codice più testabile e gestibile allo stesso tempo.
Doc Brown,

5
@Warbo Questo era l'originale e probabilmente è ancora l'unico uso valido del deridere. Anche ai confini del sistema, raramente è necessario. Le persone perdono davvero molto tempo a creare e aggiornare test quasi senza valore.
Frank Hileman,

6
@CarlLeth: ok, ora vedo da dove viene il malinteso. Stai parlando di inversione di dipendenza . Ma la domanda, questa risposta e i miei commenti riguardano DI = iniezione di dipendenza .
Doc Brown,

36

Nella mia esperienza, ci sono diversi aspetti negativi dell'iniezione di dipendenza.

Innanzitutto, l'utilizzo di DI non semplifica i test automatici tanto quanto pubblicizzato. L'unità di test di una classe con un'implementazione fittizia di un'interfaccia consente di convalidare il modo in cui tale classe interagirà con l'interfaccia. Cioè, ti consente di testare l'unità come la classe sotto test utilizza il contratto fornito dall'interfaccia. Tuttavia, ciò fornisce una garanzia molto maggiore che l'input dalla classe sotto test nell'interfaccia è come previsto. Fornisce una scarsa garanzia che la classe sottoposta a test risponda come previsto all'output dall'interfaccia poiché si tratta di un output quasi universalmente simulato, che è esso stesso soggetto a bug, semplificazioni eccessive e così via. In breve, NON consente di convalidare che la classe si comporterà come previsto con una reale implementazione dell'interfaccia.

In secondo luogo, DI rende molto più difficile navigare nel codice. Quando si tenta di passare alla definizione delle classi utilizzate come input per le funzioni, un'interfaccia può essere qualsiasi cosa, da un fastidio minore (ad es. Dove c'è una singola implementazione) ad un grande intervallo di tempo (ad es. Quando viene utilizzata un'interfaccia eccessivamente generica come IDisposable) quando si cerca di trovare l'implementazione effettiva utilizzata. Ciò può trasformare un semplice esercizio del tipo "Ho bisogno di correggere un'eccezione di riferimento null nel codice che si verifica subito dopo la stampa di questa dichiarazione di registrazione" in uno sforzo di un giorno.

In terzo luogo, l'uso di DI e framework è un'arma a doppio taglio. Può ridurre notevolmente la quantità di codice piastra caldaia necessaria per le operazioni comuni. Tuttavia, ciò comporta il bisogno di una conoscenza dettagliata del particolare framework DI per comprendere come queste operazioni comuni sono effettivamente collegate tra loro. Comprendere come le dipendenze vengono caricate nel framework e aggiungere una nuova dipendenza nel framework da iniettare può richiedere la lettura di una buona quantità di materiale di base e seguire alcuni tutorial di base sul framework. Questo può trasformare alcune semplici attività in attività piuttosto dispendiose in termini di tempo.


Vorrei anche aggiungere che più si inietta, maggiori saranno i tempi di avvio. La maggior parte dei framework DI crea tutte le istanze singleton iniettabili al momento dell'avvio, indipendentemente da dove vengono utilizzate.
Rodney P. Barbati,

7
Se desideri testare una classe con un'implementazione reale (non una simulazione), puoi scrivere test funzionali - test simili a test unitari, ma non usando simulazioni.
BЈовић

2
Penso che il tuo secondo paragrafo debba essere più sfumato: DI in sé non rende difficile (er) navigare attraverso il codice. Nella sua forma più semplice, DI è semplicemente una conseguenza del seguire SOLID. Ciò che aumenta la complessità è l'uso di riferimenti indiretti e DI framework non necessari . Oltre a ciò, questa risposta colpisce l'unghia sulla testa.
Konrad Rudolph,

4
L'iniezione di dipendenza, al di fuori dei casi in cui è realmente necessaria, è anche un segnale di avvertimento che altri codici superflui possono essere presenti in grande abbondanza. I dirigenti sono spesso sorpresi nell'apprendere che gli sviluppatori aggiungono complessità per motivi di complessità.
Frank Hileman,

1
+1. Questa è la vera risposta alla domanda che è stata posta e dovrebbe essere la risposta accettata.
Mason Wheeler,

13

Ho seguito il consiglio di Mark Seemann di "Iniezione di dipendenze in .NET" - per ipotizzare.

DI dovrebbe essere usato quando si ha una "dipendenza volatile", ad esempio c'è una ragionevole possibilità che possa cambiare.

Quindi, se pensi di poter avere più di un'implementazione in futuro o l'implementazione potrebbe cambiare, usa DI. Altrimenti newva bene.


5
Nota che offre anche diversi consigli per OO e linguaggi funzionali blog.ploeh.dk/2017/01/27/…
jk.

1
Questo è un buon punto. Se creiamo interfacce per ogni dipendenza per impostazione predefinita, allora è in qualche modo contro YAGNI.
Landeeyo,

4
Potete fornire un riferimento a "Iniezione delle dipendenze in .NET" ?
Peter Mortensen,

1
Se stai testando le unità, è molto probabile che la tua dipendenza sia volatile.
Jaquez,

11
Il bello è che gli sviluppatori sono sempre in grado di prevedere il futuro con la massima precisione.
Frank Hileman,

5

La mia più grande pipì su DI è stata già menzionata in poche risposte in modo passante, ma mi espanderò un po 'qui. DI (come è fatto principalmente oggi, con contenitori ecc.) Davvero, VERAMENTE danneggia la leggibilità del codice. E la leggibilità del codice è probabilmente il motivo alla base della maggior parte delle innovazioni di programmazione odierne. Come qualcuno ha detto - scrivere il codice è facile. La lettura del codice è difficile. Ma è anche estremamente importante, a meno che tu non stia scrivendo una sorta di piccola utilità usa e getta write-once.

Il problema con DI in questo senso è che è opaco. Il contenitore è una scatola nera. Gli oggetti appaiono semplicemente da qualche parte e non hai idea - chi li ha costruiti e quando? Cosa è stato passato al costruttore? Con chi condivido questa istanza? Chissà...

E quando lavori principalmente con le interfacce, tutte le funzionalità "vai alla definizione" del tuo IDE vanno in fumo. È terribilmente difficile tracciare il flusso del programma senza eseguirlo e passare semplicemente per vedere QUALE implementazione dell'implementazione dell'interfaccia è stata utilizzata in QUESTO particolare punto. E a volte c'è qualche ostacolo tecnico che ti impedisce di attraversare. E anche se puoi, se implica passare attraverso le viscere contorte del contenitore DI, l'intera faccenda diventa rapidamente un esercizio di frustrazione.

Per lavorare in modo efficiente con un pezzo di codice che utilizzava DI, devi conoscerlo e già sapere cosa va dove.


3

Abilitazione rapida delle implementazioni di commutazione (ad esempio DbLogger anziché ConsoleLogger)

Mentre DI in generale è sicuramente una buona cosa, suggerirei di non usarlo ciecamente per tutto. Ad esempio, non ho mai iniettato logger. Uno dei vantaggi di DI è rendere esplicite e chiare le dipendenze. Non ha senso elencare ILoggercome dipendenza di quasi tutte le classi - è solo disordine. È responsabilità del logger fornire la flessibilità di cui hai bisogno. Tutti i miei logger sono membri finali statici, potrei considerare di iniettare un logger quando ne ho bisogno di uno non statico.

Aumento del numero di classi

Questo è uno svantaggio del quadro DI o del modello beffardo dato, non di DI stesso. Nella maggior parte dei casi le mie lezioni dipendono da classi concrete, il che significa che non c'è bisogno di un boilerplate. Guice (un framework Java DI) associa di default una classe a se stessa e ho solo bisogno di sovrascrivere il binding nei test (o invece collegarli manualmente).

Creazione di interfacce non necessarie

Creo le interfacce solo quando necessario (il che è piuttosto raro). Questo significa che a volte devo sostituire tutte le occorrenze di una classe con un'interfaccia, ma l'IDE può farlo per me.

Dovremmo usare l'iniezione delle dipendenze quando abbiamo una sola implementazione?

Sì, ma evita qualsiasi piastra aggiuntiva .

Dovremmo vietare la creazione di nuovi oggetti tranne quelli di linguaggio / framework?

No. Ci saranno molte classi di valore (immutabili) e dati (mutabili), in cui le istanze vengono appena create e passate in giro e dove non ha senso iniettarle, poiché non vengono mai archiviate in un altro oggetto (o solo in altri tali oggetti).

Per loro, potrebbe essere necessario iniettare una fabbrica, ma la maggior parte delle volte non ha senso (immagina, ad esempio @Value class NamedUrl {private final String name; private final URL url;}; non hai davvero bisogno di una fabbrica qui e non c'è nulla da iniettare).

L'iniezione di una singola implementazione è una cattiva idea (supponiamo di avere una sola implementazione, quindi non vogliamo creare un'interfaccia "vuota") se non prevediamo di testare un'unità di una determinata classe?

IMHO va bene fintanto che non provoca gonfiore del codice. Iniettare la dipendenza ma non creare l'interfaccia (e nessun XML di configurazione pazzo!) In quanto è possibile farlo in seguito senza problemi.

In realtà, nel mio progetto attuale, ci sono quattro classi (su centinaia), che ho deciso di escludere da DI in quanto sono semplici classi utilizzate in troppi luoghi, inclusi gli oggetti dati.


Un altro svantaggio della maggior parte dei framework DI è l'overhead di runtime. Questo può essere spostato in fase di compilazione (per Java, c'è Dagger , nessuna idea di altre lingue).

Ancora un altro svantaggio è la magia che sta accadendo ovunque, che può essere ridotta (ad esempio, ho disabilitato la creazione di proxy durante l'uso di Guice).


-4

Devo dire che, secondo me, l'intera nozione di Iniezione di dipendenza è sopravvalutata.

DI è l'equivalente moderno dei valori globali. Le cose che stai iniettando sono singleton globali e oggetti di puro codice, altrimenti non potresti iniettarli. La maggior parte degli usi di DI sono obbligati a utilizzare una determinata libreria (JPA, Spring Data, ecc.). Per la maggior parte, DI fornisce l'ambiente perfetto per nutrire e coltivare gli spaghetti.

In tutta onestà, il modo più semplice per testare una classe è garantire che tutte le dipendenze vengano create in un metodo che può essere ignorato. Quindi creare una classe Test derivata dalla classe effettiva e sovrascrivere quel metodo.

Quindi si crea un'istanza della classe Test e si verificano tutti i suoi metodi. Questo non sarà chiaro per alcuni di voi - i metodi che state testando sono quelli appartenenti alla classe sottoposta a test. E tutti questi test di metodo si verificano in un singolo file di classe: la classe di unit test associata alla classe in prova. Non ci sono spese generali qui: ecco come funziona il test unitario.

Nel codice, questo concetto è simile al seguente ...

class ClassUnderTest {

   protected final ADependency;
   protected final AnotherDependency;

   // Call from a factory or use an initializer 
   public void initializeDependencies() {
      aDependency = new ADependency();
      anotherDependency = new AnotherDependency();
   }
}

class TestClassUnderTest extends ClassUnderTest {

    @Override
    public void initializeDependencies() {
      aDependency = new MockitoObject();
      anotherDependency = new MockitoObject();
    }

    // Unit tests go here...
    // Unit tests call base class methods
}

Il risultato è esattamente equivalente all'utilizzo di DI, ovvero ClassUnderTest è configurato per il test.

Le uniche differenze sono che questo codice è completamente conciso, completamente incapsulato, più facile da codificare, più facile da capire, più veloce, usa meno memoria, non richiede una configurazione alternativa, non richiede alcun framework, non sarà mai la causa di una pagina 4 (WTF!) Stack stack che include esattamente le classi ZERO (0) che hai scritto ed è completamente ovvio per chiunque abbia anche la minima conoscenza di OO, dal principiante al Guru (penseresti, ma ti sbaglieresti).

Detto questo, ovviamente non possiamo usarlo: è troppo ovvio e non abbastanza alla moda.

Alla fine della giornata, tuttavia, la mia più grande preoccupazione con DI è quella dei progetti che ho visto fallire miseramente, tutti sono stati enormi basi di codice in cui DI era la colla che teneva tutto insieme. DI non è un'architettura - è davvero rilevante solo in una manciata di situazioni, la maggior parte delle quali sono costrette a utilizzare l'utente per utilizzare un'altra libreria (JPA, Spring Data, ecc.). Per la maggior parte, in una base di codice ben progettata, la maggior parte degli usi di DI si verificherebbe a un livello inferiore al luogo in cui si svolgono le attività di sviluppo quotidiano.


6
Non hai descritto l'equivalente, ma l'opposto dell'iniezione di dipendenza. Nel tuo modello, ogni oggetto deve conoscere l'implementazione concreta di tutte le sue dipendenze, ma usando DI che diventa la responsabilità del componente "principale" - per incollare insieme implementazioni appropriate. Tenere presente che DI va di pari passo con l' altra inversione di DI - dipendenza, in cui non si desidera che i componenti di alto livello abbiano forti dipendenze dai componenti di basso livello.
Logan Pickup,

1
è pulito quando hai solo un livello di eredità di classe e un livello di dipendenze. Sicuramente si trasformerà in un inferno sulla terra mentre si espande?
Ewan,

4
se usi le interfacce e sposti initializeDependencies () nel costruttore è lo stesso ma più ordinato. Il passaggio successivo, l'aggiunta di parametri di costruzione significa che puoi eliminare tutti i tuoi TestClass.
Ewan,

5
C'è così tanto di sbagliato in questo. Come altri hanno già detto, il tuo esempio di "equivalente DI" non è affatto l'iniezione di dipendenza, è l'antitesi e dimostra una completa mancanza di comprensione del concetto e introduce anche altre potenziali insidie: gli oggetti parzialmente inizializzati sono un odore di codice Come Ewan suggerisce di spostare l'inizializzazione al costruttore e passarli tramite i parametri del costruttore. Allora hai DI ...
Mr.Mindor,

3
Aggiungendo a @ Mr.Mindor: esiste un "accoppiamento sequenziale" anti-pattern più generale che non si applica solo all'inizializzazione. Se i metodi di un oggetto (o, più in generale, le chiamate di un'API) devono essere eseguiti in un ordine particolare, ad esempio barpossono essere chiamati solo dopo foo, allora si tratta di un'API errata. Sta affermando di fornire funzionalità ( bar), ma non possiamo effettivamente usarlo (poiché foopotrebbe non essere stato chiamato). Se vuoi attenersi al tuo modello initializeDependencies(anti?), Dovresti almeno renderlo privato / protetto e chiamarlo automaticamente dal costruttore, quindi l'API è sincera.
Warbo,

-6

Davvero la tua domanda si riduce a "Il test unitario è negativo?"

Il 99% delle classi alternative da iniettare saranno beffe che consentiranno il test unitario.

Se si eseguono test di unità senza DI, si ha il problema di come far sì che le classi utilizzino dati derisi o un servizio deriso. Diciamo 'parte della logica' in quanto potresti non separarla in servizi.

Ci sono modi alternativi per farlo, ma DI è buono e flessibile. Una volta che lo hai inserito per il test, sei praticamente costretto a usarlo ovunque, poiché hai bisogno di qualche altro pezzo di codice, anche la cosiddetta "DI di un uomo povero" che istanzia i tipi concreti.

È difficile immaginare uno svantaggio così grave che il vantaggio del test unitario sia sopraffatto.


13
Non sono d'accordo con la tua affermazione che DI riguarda i test unitari. Facilitare i test unitari è solo uno dei vantaggi di DI, e probabilmente non è il più importante.
Robert Harvey,

5
Non sono d'accordo con la tua premessa che i test unitari e DI sono così vicini. Usando un mock / stub, facciamo in modo che la suite di test ci menti un po 'di più: il sistema sotto test si allontana dal sistema reale. È oggettivamente negativo. A volte questo è compensato da un lato positivo: le chiamate beffate del FS non richiedono pulizia; le richieste HTTP derise sono veloci, deterministiche e funzionano offline; ecc. Al contrario, ogni volta che utilizziamo un newmetodo hardcoded all'interno di un metodo, sappiamo che lo stesso codice in esecuzione in produzione era in esecuzione durante i test.
Warbo,

8
No, non è "il test unitario è negativo?", È "è davvero beffardo (a) davvero, e (b) vale l'incremento della complessità sostenuto?" Questa è una domanda molto diversa. Il test unitario non è male (letteralmente nessuno lo sta discutendo), e ne vale sicuramente la pena in generale. Ma non tutti i test unitari richiedono una derisione e la derisione comporta un costo sostanziale, quindi dovrebbe almeno essere usata con giudizio.
Konrad Rudolph,

6
@Ewan Dopo il tuo ultimo commento, non credo che siamo d'accordo. Sto dicendo che la maggior parte dei test unitari non ha bisogno di DI [framework] , perché la maggior parte dei test unitari non ha bisogno di essere derisi. In effetti, lo userei anche come euristico per la qualità del codice: se la maggior parte del tuo codice non può essere testato in unità senza oggetti DI / mock, allora hai scritto un codice errato che è stato giustamente accoppiato. La maggior parte del codice dovrebbe essere altamente disaccoppiato, responsabilità singola e scopo generale ed essere banalmente testabile in isolamento.
Konrad Rudolph,

5
@Ewan Il tuo link fornisce una buona definizione di unit test. In base a tale definizione il mio Orderesempio è un unit test: sta testando un metodo (il totalmetodo di Order). Ti stai lamentando che sta chiamando il codice da 2 classi, la mia risposta è e allora ? Non stiamo testando "2 classi contemporaneamente", stiamo testando il totalmetodo. Non dovremmo preoccuparci di come un metodo faccia il suo lavoro: quelli sono dettagli di implementazione; testarli causa fragilità, accoppiamento stretto, ecc. Ci preoccupiamo solo del comportamento del metodo (valore di ritorno ed effetti collaterali), non delle classi / moduli / registri CPU / ecc. utilizzato nel processo.
Warbo,
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.