16

Dopo tutto quello che ho letto su Dependency Injection e IoC ho deciso di provare ad utilizzare Windsor Container all'interno della nostra applicazione (è un'applicazione web multistrato 50K LOC, quindi spero che non sia eccessivo). Ho usato una semplice classe statica per il wrapping del contenitore e l'ho inizializzata all'avvio dell'app, che funziona abbastanza bene per ora.Quale dovrebbe essere la strategia di test unitario quando si utilizza IoC?

La mia domanda riguarda il test dell'unità. So che DI mi renderà la mia vita molto più facile dandomi la possibilità di iniettare nella classe sotto test le implementazioni stub/simulate dei collaboratori di classe. Ho già scritto un paio di test usando questa tecnica e sembra avere senso per me. Quello di cui non sono sicuro è se dovrei usare IoC (in questo caso Windsor Castle) anche in unit test (probabilmente in qualche modo configurarlo per restituire stub/mock per i miei casi speciali) o è meglio cablare tutte le dipendenze manualmente nei test. Cosa ne pensi e quale pratica ha funzionato per te?

+2

Duplicato: http://stackoverflow.com/questions/1465849/using-ioc-for-unittesting –

+0

Grazie, non ho potuto trovalo ovunque;) –

risposta

20

Non è necessario il contenitore DI nei test di unità perché le dipendenze vengono fornite tramite oggetti fittizi generati con framework come Rhino Mocks o Moq. Quindi, ad esempio, quando si verifica una classe che ha una dipendenza su qualche interfaccia, questa dipendenza viene solitamente fornita tramite l'iniezione del costruttore.

public class SomeClassToTest 
{ 
    private readonly ISomeDependentObject _dep; 
    public SomeClassToTest(ISomeDependentObject dep) 
    { 
     _dep = dep; 
    } 

    public int SomeMethodToTest() 
    { 
     return _dep.Method1() + _dep.Method2(); 
    } 
} 

Nella propria applicazione che si intende utilizzare un quadro DI passare qualche reale attuazione ISomeDependentObject nel costruttore che si potrebbero avere dipendenze da altri oggetti, mentre in una prova di unità si crea un oggetto fittizio perché si vuole solo mettere alla prova questa classe in isolamento. Esempio con Rhino Mocks:

[TestMethod] 
public void SomeClassToTest_SomeMethodToTest() 
{ 
    // arrange 
    var depStub = MockRepository.CreateStub<ISomeDependentObject>(); 
    var sut = new SomeClassToTest(depStub); 
    depStub.Stub(x => x.Method1()).Return(1); 
    depStub.Stub(x => x.Method2()).Return(2); 

    // act 
    var actual = sut.SomeMethodToTest(); 

    // assert 
    Assert.AreEqual(3, actual); 
} 
+0

Sembra ragionevole. Dopotutto, questo è esattamente quello che sto facendo. Non ero sicuro se sia una buona pratica, soprattutto quando ci sono molte dipendenze e devo schernirle manualmente tutte. –

+0

Dai un'occhiata alla funzione di automocking di cui ho parlato nella mia risposta Thomas, semplifica davvero le classi di test con molte dipendenze. :-) –

2

Ho appena scritto un'app molto simile per dimensioni e stile. Non inserirò alcuna iniezione di dipendenza nei test di unità perché non è abbastanza complicato da essere necessario. Dovresti usare una struttura di simulazione per creare i tuoi mock (RhinoMocks/Moq).

Anche Automocking in Moq o Auto Mock Container in Rhinomocks semplificherà ulteriormente la costruzione dei vostri mock.

Auto beffardo consente di ottenere oggetto del tipo che si desidera testare, senza creazione di mock a mano. Tutte le dipendenze vengono automaticamente ridotte automaticamente (supponendo che si tratti di interfacce) e immesse nel costruttore del tipo. Se è il necessario è possibile impostare il comportamento previsto , ma non è necessario.

+0

Sì, ma il contenitore Auto Mock non è anche un contenitore IoC? ;) È solo che è più rilassato per quanto riguarda la risoluzione delle dipendenze: genererà un'implementazione fittizia predefinita dei servizi senza generare eccezioni per cose non risolte come il contenitore "appropriato" di Windsor. –

+1

... e soprattutto ci vogliono solo due righe per creare! Il fatto che non sia necessario registrare alcuna dipendenza significa che è molto più semplice di un normale IoC. Un altro motivo per usare l'automocking è di astrarre la chiamata all'oggetto sotto il costruttore del test. Pertanto, se la firma cambia, non è necessario modificare molti test, rendendo le classi di test meno fragili. :-) –

3

Sto lavorando a un progetto ASP.NET MVC con circa 400 test di unità. Sto usando Ninject per l'iniezione delle dipendenze e MBUnit per i test.

Ninject non è realmente necessario per il test dell'unità, ma riduce la quantità di codice che devo digitare. Devo solo specificare una volta (per progetto) come le mie interfacce dovrebbero essere istanziate invece di fare ciò ogni volta che inizializzo la classe sottoposta a test.

Per risparmiare tempo sulla scrittura di nuovi test, ho creato classi di base di test fixture con il maggior numero possibile di codice di configurazione generico. Le procedure di installazione in quelle classi intializzano i repository fasulli, creano alcuni dati di test e un'identità falsa per l'utente di test. I test unitari inizializzano solo i dati che sono troppo specifici per entrare nelle procedure di configurazione generiche.

In alcuni test sto anche prendendo in giro oggetti (al contrario di falsi), ma ho scoperto che la falsificazione dei repository di dati comporta meno lavoro e test più accurati.

Ad esempio, sarebbe più difficile verificare se il metodo in prova esegue correttamente il commit di tutti gli aggiornamenti nel repository dopo averli creati quando si utilizza un repository mock rispetto a quando si utilizza un repository falso.

È stato un po 'di lavoro da impostare all'inizio, ma mi ha davvero aiutato a ridurre a lungo termine risparmiare molto tempo.

0

Come ha già sottolineato Darin, non è necessario utilizzare DI se si dispone di mock. (Tuttavia, DI ha anche altri vantaggi, tra cui, prima di tutto, diminuire le dipendenze nel codice, il che rende molto più facile mantenere ed estendere il codice a lungo termine.)

Personalmente preferisco cablare tutto nei miei test unitari, basandomi il meno possibile su framework esterni, file di configurazione, ecc.

Problemi correlati