La Dependency Injection vale la pena al di fuori di UnitTesting


17

Dato un costruttore che non dovrà mai e poi mai usare diverse implementazioni di diversi oggetti che inizializza, è ancora pratico usare DI? Dopotutto, potremmo ancora voler effettuare unit test.

La classe in questione inizializza alcune altre classi nel suo costruttore e le classi che utilizza sono piuttosto specifiche. Non utilizzerà mai un'altra implementazione. Siamo giustificati nell'evitare di provare a programmare su un'interfaccia?



3
BACIO - è sempre l'approccio migliore.
Reactgular,

5
A mio avviso, questa domanda è più specifica del duplicato proposto. design-for-future-changes-or-risolvere-the-problem-at-hand , quindi voto per non chiudere questo
k3b

1
La testabilità non è sufficiente per una ragione?
ConditionRacer

Non suona davvero come un duplicato, ma come rispondere a se stesso. Se non accadrà mai e poi mai, progettare per esso non sta progettando cambiamenti futuri. Anche gli astronauti dell'architettura progettano solo cambiamenti che non accadranno mai per cattiva valutazione.
Steve314,

Risposte:


13

Dipende da quanto sei sicuro che "mai, mai" sia corretto (durante il periodo in cui l'app verrà utilizzata).

In generale:

  • Non modificare il progetto solo per testabilità. I test unitari sono lì per servirti, non viceversa.
  • Non ripeterti. Fare un'interfaccia che avrà sempre un solo erede è inutile.
  • Se una classe non è verificabile, probabilmente non è flessibile / estensibile nemmeno per casi d'uso reali. A volte va bene - è improbabile che tu abbia bisogno di quella flessibilità, ma semplicità / affidabilità / manutenibilità / prestazioni / qualunque cosa sia più importante.
  • Se non lo sai, codifica l'interfaccia. I requisiti cambiano.

12
Quasi ti ho dato -1 per "non modificare il tuo progetto solo per testabilità". Per ottenere la testabilità è IMHO uno dei motivi principali per cambiare un design! Ma i tuoi altri punti sono ok.
Doc Brown,

5
@docBrown - (e altri) Qui differisco da molti, e forse anche dalla maggioranza. ~ 95% delle volte, rendere le cose testabili le rende anche flessibili o estensibili. Dovresti averlo nel tuo design (o aggiungerlo al tuo design) per la maggior parte del tempo - la testabilità deve essere dannata. L'altro ~ 5% o ha requisiti strani che impediscono questo tipo di flessibilità a favore di qualcos'altro, oppure è così essenziale / immutabile che scoprirai abbastanza rapidamente se è rotto, anche senza test dedicati.
Telastyn,

4
può essere sicuro, ma la tua prima affermazione sopra potrebbe essere interpretata erroneamente troppo facilmente come "lascia un codice mal progettato come quando la tua unica preoccupazione è la testabilità".
Doc Brown,

1
Perché diresti "Fare un'interfaccia che avrà sempre un solo erede è inutile"? L'interfaccia può funzionare come un contratto.
kaptan,

2
@kaptan - perché l'oggetto concreto può funzionare allo stesso modo di un contratto. Fare due significa solo che è necessario scrivere il codice due volte (e modificarlo due volte quando cambia). Certo, la stragrande maggioranza delle interfacce avrà implementazioni multiple (non di prova) o dovrete prepararvi per quella eventualità. Ma per quel raro caso in cui tutto ciò che serve è un semplice oggetto sigillato, basta usarlo direttamente.
Telastyn,

12

Siamo giustificati nell'evitare di provare a programmare su un'interfaccia?

Mentre il vantaggio della codifica rispetto a un'interfaccia è abbastanza evidente, dovresti chiederti cosa si ottiene esattamente non facendolo.

La domanda successiva è: fa parte della responsabilità della classe in questione scegliere le implementazioni o no? Questo può o non può essere il caso e dovresti agire di conseguenza.

In definitiva, c'è sempre il potenziale per qualche sciocco vincolo che esce dal nulla. Potresti voler eseguire lo stesso codice contemporaneamente, e la possibilità di iniettare l'implementazione potrebbe aiutare con la sincronizzazione. Oppure, dopo aver creato il profilo della tua app, potresti scoprire che desideri una strategia di allocazione diversa dalla semplice istanza. Oppure emergono preoccupazioni trasversali e non hai il supporto AOP a portata di mano. Chissà?

YAGNI suggerisce che quando si scrive il codice, è importante non aggiungere nulla di superfluo. Una delle cose che i programmatori tendono ad aggiungere al loro codice senza nemmeno esserne consapevoli, sono ipotesi superflue. Come "questo metodo potrebbe tornare utile" o "questo non cambierà mai". Entrambi aggiungono disordine al tuo design.


2
+1 per citare YAGNI qui come argomento per DI, non contro di essa.
Doc Brown,

4
@DocBrown: considerando che YAGNI può essere utilizzato qui, anche per giustificare il non utilizzo di DI. Penso che sia più un argomento contro YAGNI essere una misura utile per qualsiasi cosa.
Steven Evers,

1
+1 per le ipotesi superflue incluse in YAGNI, che ci hanno colpito un paio di volte in testa
Izkata,

@SteveEvers: Penso che l'uso di YAGNI per giustificare l'ignoranza del principio di inversione di dipendenza precluda una mancanza di comprensione di YAGNI o DIP o forse anche alcuni dei principi fondamentali di programmazione e ragionamento. Dovresti ignorare il DIP solo se necessario - una circostanza in cui YAGNI non è semplicemente applicabile per sua stessa natura.
back2dos,

@ back2dos: supposizione che DI sia utile a causa di "forse-requisiti" e "chi lo sa?" è precisamente il treno del pensiero che YAGNI è destinato a combattere, invece lo stai usando come giustificazione per ulteriori strumenti e infrastrutture.
Steven Evers,

7

Dipende da vari parametri ma qui ci sono alcuni motivi per cui potresti voler usare DI:

  • il test è piuttosto una buona ragione in sé credo
  • le classi richieste dal tuo oggetto potrebbero essere utilizzate in altri luoghi - se le inietti ti consente di mettere in comune le risorse (ad esempio se le classi in questione sono thread o connessioni al database - non vuoi che ciascuna delle tue classi crei nuove connessioni)
  • se l'inizializzazione di quegli altri oggetti non riesce, il codice più in alto nello stack sarà probabilmente migliore nel gestire i problemi rispetto alla tua classe, che probabilmente inoltrerà semplicemente gli errori

In conclusione: probabilmente puoi vivere senza DI in quel caso, ma ci sono possibilità che l'uso di DI finisca in un codice più pulito.


3

Quando progetti un programma o una funzione, parte del processo di pensiero dovrebbe essere il modo in cui lo testerai. L'iniezione di dipendenza è uno degli strumenti di test e dovrebbe essere considerata come parte del mix.

I vantaggi aggiuntivi di Dependency Injection e l'uso delle interfacce anziché delle classi sono utili anche quando un'applicazione viene estesa, ma possono anche essere adattati al codice sorgente in un secondo momento in modo abbastanza semplice e sicuro usando la ricerca e la sostituzione. - anche su progetti molto grandi.


2
 > Dependency Injection worth it **outside** of UnitTesting?
 > Are we justified in avoiding trying to program to an interface?

Molte risposte a questa domanda possono essere discusse come "potresti averne bisogno perché .." come YAGNI se non vuoi fare unittest.

Se stai chiedendo quali motivi oltre agli unittest richiedono di programmare su un'interfaccia

, l'iniezione di dipendenza ne vale la pena al di fuori di UnitTesting se è necessaria l' inversione del controllo . Ad esempio, se l'implementazione di un modulo necessita di un altro modulo non accessibile nel suo livello.

Esempio: se hai i moduli gui=> businesslayer=> driver=> common.

In questo scenario è businesslayerpossibile utilizzare driverma drivernon è possibile utilizzare businesslayer.

Se è drivernecessaria una funzionalità di livello superiore, è possibile implementare questa funzionalità in businesslayercui implementa un'interfaccia a commonlivello. driverdeve solo conoscere l'interfaccia in commonlayer.


1

Sì, in primo luogo, dimentica i test unitari come motivo per progettare il tuo codice attorno agli strumenti di test unitari, non è mai una buona idea piegare il design del tuo codice per adattarlo a un vincolo artificiale. Se i tuoi strumenti ti costringono a farlo, ottieni strumenti migliori (ad esempio Microsoft Fakes / Moles che ti consentono molte più opzioni per la creazione di oggetti finti).

Ad esempio, divideresti le tue classi in metodi solo pubblici solo perché gli strumenti di test non funzionano con metodi privati? (So ​​che la saggezza prevalente è far finta di non aver bisogno di testare metodi privati, ma ritengo che questa sia una reazione alla difficoltà nel farlo con gli strumenti attuali, non una reazione reale alla non necessità di testare i privati).

Tutto sommato, dipende da che tipo di TDDer sei: il "mockist" come li descrive Fowler , deve cambiare il codice per adattarlo agli strumenti che usano, mentre i tester "classici" creano test che sono più integrati nella natura (ad esempio testare la classe come unità, non ogni metodo), quindi vi è meno necessità di interfacce, specialmente se si utilizzano strumenti che possono deridere classi concrete.


3
Non credo che la saggezza prevalente sul fatto che i metodi privati ​​non debbano essere testati non abbia nulla a che fare con la difficoltà di testarli. Se si è verificato un errore in un metodo privato, ma non ha influito sul comportamento dell'oggetto, non ha alcun effetto. Se in un metodo privato si verificava un bug che influiva sul comportamento dell'oggetto, allora c'è un test fallito o mancante su almeno uno dei metodi pubblici di un oggetto.
Michael Shaw,

Ciò dipende dal fatto che si verifichi un comportamento o uno stato. I metodi privati ​​devono essere testati in qualche modo, ovviamente, ma se sei un TDD classico, li testerai testando la classe "nel suo insieme", gli strumenti di test che la maggior parte delle persone usano sono focalizzati sul test di ogni metodo individualmente ... e il mio punto è che questi ultimi in realtà non eseguono correttamente i test in quanto perdono l'opportunità di eseguire un test completo. Quindi sono d'accordo con te, mentre cerco di evidenziare come il TDD sia stato sovvertito da come alcune (la maggior parte?) Persone effettuano test unitari oggi.
gbjbaanb,

1

L'iniezione delle dipendenze rende anche più chiaro che il tuo oggetto HA dipendenze.

Quando qualcun altro usa il tuo oggetto in modo ingenuo (senza guardare il costruttore), scopriranno che dovranno impostare servizi, connessioni, ecc. Non erano ovvi perché non dovevano passarli al costruttore . Passare le dipendenze rende più chiaro ciò che è necessario.

Stai anche nascondendo il fatto che la tua classe potrebbe violare SRP. Se si passa in molte dipendenze (più di 3), la classe potrebbe fare troppo e dovrebbe essere rifattorizzata. Ma se li stai creando nel costruttore, questo odore di codice sarà nascosto.


0

Sono d'accordo con entrambi i campi qui e la questione è ancora aperta al dibattito secondo me. YAGNI mi chiede di non affrontare il cambiamento del mio codice per adattarlo alla supposizione. I test unitari non sono un problema qui perché credo fermamente che la dipendenza non cambierà mai e poi mai. Ma se fossi stato il test unitario come previsto per cominciare, non avrei mai raggiunto questo punto comunque. Come avrei eventualmente ridotto in DI.

Consentitemi di offrire un'altra alternativa specifica per il mio scenario

Con un modello di fabbrica posso localizzare le dipendenze in un unico posto. Durante i test posso cambiare l'implementazione in base al contesto, e questo è abbastanza buono. Questo non va contro YAGNI a causa dei benefici dell'astrazione di diverse costruzioni di classe.

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.