Quando utilizzare le interfacce (unit test, IoC?)

17

Sospetto di aver commesso un errore da scolaro qui, e sto cercando chiarimenti. Molte delle classi nella mia soluzione (C #) - oserei dire la maggior parte - ho finito per scrivere un'interfaccia corrispondente per. Per esempio. un'interfaccia "ICalculator" e una classe "Calculator" che la implementa, anche se non sono mai in grado di sostituire quella calcolatrice con un'implementazione diversa. Inoltre, la maggior parte di queste classi risiedono nello stesso progetto delle loro dipendenze - in realtà hanno solo bisogno di essere internal , ma hanno finito per essere public come effetto collaterale di implementazione delle rispettive interfacce.

Penso che questa pratica di creare interfacce per tutto derivasse da alcune falsità: -

1) Originariamente pensavo che fosse necessaria un'interfaccia per creare mock test di unità (sto usando Moq), ma da allora ho scoperto che una classe può essere derisa se i suoi membri sono virtual , e ha un costruttore senza parametri (correggimi se sbaglio).

2) Inizialmente pensavo che fosse necessaria un'interfaccia per registrare una classe con il framework IoC (Castle Windsor), ad es.

Container.Register(Component.For<ICalculator>().ImplementedBy<Calculator>()...

quando infatti potevo semplicemente registrare il tipo concreto contro se stesso:

Container.Register(Component.For<Calculator>().ImplementedBy<Calculator>()...

3) Utilizzando le interfacce, ad es. parametri di costruzione per l'iniezione di dipendenza, risultati in "accoppiamento lento".

Quindi sono impazzito con le interfacce ?! Sono a conoscenza degli scenari in cui useresti "normalmente" un'interfaccia, ad es. esponendo una API pubblica, o per cose come la funzionalità "collegabile". La mia soluzione ha un piccolo numero di classi che si adattano a tali casi d'uso, ma mi chiedo se tutte le altre interfacce non siano necessarie e debbano essere rimosse? Per quanto riguarda il punto 3) sopra, non violerò un "accoppiamento lento" se dovessi farlo?

Modifica : - Sto solo giocando con Moq, e sembra che i metodi siano pubblici e virtuali, e abbiano un pubblico costruttore senza parametri, per essere in grado di deriderli. Quindi sembra che non possa avere classi interne quindi?

    
posta Andrew Stephens 10.07.2013 - 12:04
fonte

5 risposte

7

In generale, se crei un'interfaccia che ha solo un implementatore, devi solo scrivere due volte le cose e sprecare il tuo tempo. Le interfacce da sole non forniscono un accoppiamento lento se sono strettamente accoppiate a un'implementazione ... Detto questo, se vuoi prendere in giro queste cose nei test unitari, di solito è un grande segnale che avrai inevitabilmente bisogno di più di un implementatore in real codice.

Lo considererei un po 'un odore di codice se quasi tutte le tue classi avessero interfacce. Ciò significa che quasi tutti lavorano l'un l'altro in un modo o nell'altro. Sospetto che le classi stiano facendo troppo (dal momento che non ci sono classi di helper) o che tu abbia astratto troppo (oh, voglio un'interfaccia provider per gravità poiché potrebbe cambiare!).

    
risposta data 10.07.2013 - 13:36
fonte
4

1) Anche se le classi concrete sono irrisolvibili, richiede comunque di creare membri virtual e fornire un costruttore senza parametri, che potrebbe essere invadente e indesiderato. Tu (oi nuovi membri del team) ti troverai presto ad aggiungere sistematicamente virtual e costruttori senza parametri in ogni nuova classe senza pensarci due volte, solo perché "è così che funzionano le cose".

Un'interfaccia è IMO un'idea molto migliore quando devi prendere in giro una dipendenza, perché ti permette di rinviare l'implementazione fino a quando ne hai davvero bisogno, e crea un bel contratto chiaro della dipendenza.

3) Com'è quella falsità?

Credo che le interfacce siano grandi per la definizione di protocolli di messaggistica tra oggetti collaborativi. Potrebbero esserci casi in cui la necessità per loro è discutibile, come con entità di dominio per esempio. Tuttavia, ovunque tu abbia un partner stabile (vale a dire una dipendenza iniettata rispetto a un riferimento transitorio) con cui devi comunicare, dovresti generalmente considerare l'utilizzo di un'interfaccia.

    
risposta data 10.07.2013 - 16:44
fonte
3

L'idea di IoC è quella di rendere i tipi di calcestruzzo sostituibili. Quindi, anche se (al momento) hai solo una calcolatrice che implementa ICalculator, una seconda implementazione potrebbe dipendere solo dall'interfaccia e non dai dettagli di implementazione di Calculator.

Pertanto, la prima versione della registrazione del mio IoC-Container è quella corretta e quella che dovresti usare in futuro.

Se rendi pubbliche o interne le tue classi non sono realmente correlate, e nemmeno il moqing.

    
risposta data 10.07.2013 - 12:49
fonte
2

Sarei davvero più preoccupato del fatto che sembri aver bisogno di "prendere in giro" praticamente ogni tipo dell'intero progetto.

I duplicati dei test sono utili soprattutto per isolare il codice dal mondo esterno (librerie di terze parti, IO disco, IO di rete, ecc.) o da calcoli molto costosi. Essere troppo liberali con il tuo quadro di isolamento (ne hai davvero VERAMENTE bisogno? Il tuo progetto è così complesso?) Può facilmente portare a test che sono accoppiati all'implementazione, e rende il tuo progetto più rigido. Se scrivi una serie di test che verificano che alcune classi hanno chiamato il metodo X e Y in quell'ordine, e poi hanno passato i parametri a, be c al metodo Z, ottieni VERAMENTE valore? A volte ottieni test di questo tipo quando collaudi logging o interazione con librerie di terze parti, ma dovrebbe essere l'eccezione, non la regola.

Non appena il tuo framework di isolamento ti dice che devi rendere pubbliche le cose e che devi sempre avere costruttori senza parametri, è il momento di uscire dalla scherma, perché a questo punto il tuo framework ti sta costringendo a scrivere male (peggio ) codice.

Parlando in modo più specifico delle interfacce, trovo che siano utili in alcuni casi chiave:

  • Quando devi inviare chiamate di metodi a tipi diversi, ad es. quando hai implementazioni multiple (questo è ovvio, dato che la definizione di un'interfaccia nella maggior parte delle lingue è solo una raccolta di metodi virtuali)

  • Quando devi essere in grado di isolare il resto del codice da qualche classe. Questo è il modo normale di astrarre il file system e l'accesso alla rete e il database e così via dalla logica di base della tua applicazione.

  • Quando è necessaria l'inversione del controllo per proteggere i limiti dell'applicazione. Questo è uno dei modi più potenti per gestire le dipendenze. Sappiamo tutti che moduli di alto livello e moduli di basso livello non dovrebbero conoscersi, perché cambieranno per ragioni diverse e NON si desidera avere dipendenze su HttpContext nel proprio modello di dominio o su qualche framework di rendering Pdf nella libreria di moltiplicazione di matrici . Rendere le tue classi di confine implementare le interfacce (anche se non vengono mai sostituite) aiuta a sciogliere l'accoppiamento tra i livelli, e riduce drasticamente il rischio che le dipendenze filtrino attraverso i livelli da tipi che conoscono troppi altri tipi.

risposta data 13.04.2016 - 22:11
fonte
1

Mi propongo di pensare che, se una logica è privata, l'implementazione di tale logica non dovrebbe essere di alcuna importanza per nessuno e come tale non dovrebbe essere testata. Se una logica è abbastanza complessa da essere testata, quella logica probabilmente ha un ruolo distinto e dovrebbe quindi essere pubblica. Per esempio. se estraggo del codice in un metodo di helper privato, trovo che di solito è qualcosa che potrebbe anche stare in una classe pubblica separata (un formattatore, un parser, un mapper, qualunque cosa).
Per quanto riguarda le interfacce, ho lasciato che la necessità di dover mozzare o deridere la classe mi guidi. Roba come repository, di solito voglio essere in grado di mozzare o deridere. Un mappatore in un test di integrazione, (solitamente) non così tanto.

    
risposta data 11.07.2013 - 09:10
fonte

Leggi altre domande sui tag