7

Abbiamo utilizzato Simple Injector con un buon successo, in un'applicazione abbastanza sostanziale. Abbiamo utilizzato l'iniezione del costruttore per tutte le nostre classi di produzione e configurato Simple Injector per popolare tutto e tutto è peachy.Utilizzo del contenitore DI nelle prove unitarie

Non abbiamo, tuttavia, utilizzato Simple Injector per gestire gli alberi di dipendenza per i nostri test unitari. Invece, abbiamo iniziato a fare il tutto manualmente.

Ho passato solo un paio di giorni a lavorare con un importante refactoring e quasi tutto il mio tempo era nel fissare questi alberi di dipendenza costruiti manualmente nei nostri test unitari.

Questo mi chiede: qualcuno ha qualche schema che usano per configurare gli alberi di dipendenza che usano nei test unitari? Per noi, almeno, nei nostri test i nostri alberi di dipendenza tendono ad essere abbastanza semplici, ma ce ne sono molti.

Qualcuno ha un metodo che usano per gestirli?

+0

Non so quale modello cerchi. Perché non creare solo i contenitori nell'inizializzazione di test (costruttore di xunit, ad es.)? Lo schema è semplice: composizione. – Artyom

+1

Se sei veramente interessato ai modelli di test unitario, dovresti leggere [xUnit Test Patterns] (https://www.amazon.co.uk/xUnit-Test-Patterns-Refactoring-Code-ebook/dp/B004X1D36K/ref = dp_kinw_strp_1). – Steven

risposta

12

Per i test di unità reali (ovvero quelli che testano solo una classe e prendono in giro tutte le sue dipendenze), non ha senso utilizzare un framework DI. In questi test:

  • se si scopre di avere un sacco di codice ripetitivo per new ing un'istanza della classe con tutti i mock che hai creato, una strategia utile è quella di creare tutti i tuoi deride e crea l'istanza per l'oggetto-under-test nel tuo metodo di installazione (questi possono essere tutti campi di istanze private), quindi l'area "organizza" di ogni singolo test deve semplicemente chiamare il codice appropriato Setup() sui metodi necessari per simulare. In questo modo, si finisce con una sola istruzione new PersonController(...) per classe di test.
  • se è necessario creare molti oggetti dominio/dati, è utile creare oggetti Builder che iniziano con valori sani per il test. Quindi, invece di invocare un enorme costruttore su tutto il tuo codice, con una serie di valori falsi, stai semplicemente chiamando, per esempio, var person = new PersonBuilder().Build(), possibilmente con solo poche chiamate concatenate per i pezzi di dati che ti interessano in particolare in quel test . Potresti anche essere interessato a AutoFixture, ma non l'ho mai usato quindi non posso garantire per questo.

Se si scrive integrazione test, in cui è necessario testare l'interazione tra diverse parti del sistema, ma è ancora necessario essere in grado di prendere in giro pezzi specifici, prendere in considerazione la creazione di classi Builder per i vostri servizi, quindi puoi dire, ad es var personController = new PersonControllerBuilder.WithRealDatabase(connection).WithAuthorization(new AllowAllAuthorizationService()).Build().

Se si stanno scrivendo test end-to-end o "scenari", in cui è necessario testare l'intero sistema, è quindi opportuno impostare il framework DI, sfruttando lo stesso codice di configurazione del prodotto reale utilizza. Puoi modificare leggermente la configurazione per avere un controllo programmatico migliore su cose come l'utente che ha effettuato l'accesso e così via. Puoi ancora sfruttare le altre classi di build che hai creato per la costruzione di dati.

+1

Credo che [Object Mother] (http://xunitpatterns.com/Test%20Helper.html) sia il nome del modello di builder in caso di test unitari. Bella risposta btw. +1 – Steven

+0

Risposta opposta all'indirizzo: https://softwareengineering.stackexchange.com/questions/140992/is-dependency-injection-essential-for-unit-testing – sotn

+0

@sotn: non sembra essere la stessa domanda, e Non vedo nessuna risposta che sembri contraddirla. Puoi collegare direttamente alla risposta di cui parli e spiegare come è "opposto" a questo? – StriplingWarrior

6

Astenersi dall'utilizzare il contenitore DI nei test dell'unità. Nei test di unità, si tenta di testare una classe o un modulo in isolamento e c'è poco uso per un contenitore DI in quella zona.

Le cose sono diverse con i test di integrazione, dal momento che si desidera verificare come i componenti del sistema si integrano e lavorano insieme. In tal caso, si utilizza spesso la configurazione DI di produzione e si scambiano alcuni dei servizi per servizi falsi (come ad esempio il MailService) ma si avvicina il più possibile alla cosa reale.In questo caso si utilizza il contenitore per risolvere l'intero grafico dell'oggetto.

Anche il desiderio di utilizzare un contenitore DI nei test dell'unità deriva spesso da modelli inefficaci. Ad esempio, se si tenta di creare la classe sotto test con tutte le sue dipendenze in ciascun test, si ottiene un sacco di codice di inizializzazione duplicato e un piccolo cambiamento nella classe sottoposta a test può in tal caso propagarsi attraverso il sistema e richiedere di cambia dozzine di test unitari. Questo ovviamente causa problemi di manutenibilità.

Uno schema che mi ha aiutato molto in passato è l'uso di un semplice metodo di prova specifico per classe. Questo metodo centralizza la creazione della classe sottoposta a test e riduce al minimo la quantità di modifiche da apportare quando le dipendenze della classe in prova cambiano. Questo è il modo come metodo factory potrebbe apparire come:

private ClassUnderTest CreateValidClassUnderTest(params object[] dependencies) { 
    return new ClassUnderTest(
     dependencies.OfType<ILogger>().SingleOrDefault() ?? new FakeLogger(), 
     dependencies.OfType<IMailSender>().SingleOrDefault() ?? new FakeMailer(), 
     dependencies.OfType<IEventPublisher>().SingleOrDefault() ?? new FakePublisher()); 
} 

Questo metodo factory contiene una matrice params che accetta qualsiasi dipendenza. Il codice estrae le dipendenze dall'elenco e nel caso in cui manchi una particolare dipendenza, verrà iniettata una nuova implementazione falsa.

Questo funziona, poiché nella maggior parte dei test si è interessati solo a una o due dipendenze. Le altre dipendenze sono necessarie per il funzionamento della classe, ma non sono interessanti per il test specifico. Il metodo factory consente quindi di fornire solo le dipendenze che sono interessanti per il test in corso, rimuovendo il rumore delle dipendenze non utilizzate. Il metodo factory permette quindi di scrivere il seguente test:

public void Test() { 
    // Arrange 
    var logger = new ListLogger(); 

    var cut = CreateValidClassUnderTest(logger); 

    // Act 
    cut.DoSomething(); 

    // Arrange 
    Assert.IsTrue(logger.Count > 0);  
} 

Se siete interessati ad imparare a scrivere leggibili test affidabile e mantenibile, vi consiglio di leggere il libro di Roy Osherove The Art of Unit Testing (seconda edizione). Questo mi ha aiutato moltissimo.