7

Wir haben den Simple Injector mit gutem Erfolg in einer ziemlich umfangreichen Anwendung eingesetzt. Wir haben die Konstruktorinjektion für alle unsere Produktionsklassen verwendet und den Simple Injector so konfiguriert, dass alles ausgefüllt wird, und alles ist prächtig.Verwendung von DI-Containern in Unit-Tests

Wir haben jedoch Simple Injector nicht verwendet, um die Abhängigkeitsbäume für unsere Komponententests zu verwalten. Stattdessen haben wir alles manuell neu erstellt.

Ich habe gerade ein paar Tage damit verbracht, ein größeres Refactoring durchzuarbeiten, und fast meine gesamte Zeit bestand darin, diese manuell erstellten Abhängigkeitsbäume in unseren Komponententests zu reparieren.

Das hat mich wundern - hat jemand irgendwelche Muster, die sie verwenden, um die Abhängigkeitsbäume zu konfigurieren, die sie in Komponententests verwenden? Zumindest für uns sind unsere Abhängigkeitsbäume in unseren Tests relativ einfach, aber es gibt eine Menge von ihnen.

Wer hat eine Methode, um diese zu verwalten?

+0

Nicht sicher, welches Muster Sie suchen. Warum machen Sie nicht einfach Ihre Container in der Testinitialisierung (Konstruktor für xunit, z. B.)? Das Muster ist einfach - Zusammensetzung. – Artyom

+1

Wenn Sie wirklich an Einheitentestmustern interessiert sind, sollten Sie [xUnit Test Patterns] lesen (https://www.amazon.de/xUnit-Test-Patterns-Refactoring-Code-ebook/dp/B004X1D36K/ref = dp_kinw_strp_1). – Steven

Antwort

12

Für echte Komponententests (d. H. Diejenigen, die nur eine Klasse testen und alle ihre Abhängigkeiten vortäuschen), macht es keinen Sinn, ein DI-Framework zu verwenden. Bei diesen Tests:

  • , wenn Sie feststellen, dass Sie eine Menge von sich wiederholenden Code für new haben ing eine Instanz der Klasse mit all den Mocks Sie erstellt haben, eine nützliche Strategie ist es, alle Ihre Mocks zu erstellen und Erstellen Sie die Instanz für den zu testenden Subjekt in Ihrer Setup-Methode (dies können alles private Instanzfelder sein), und dann muss der "Anordnungs" -Bereich jedes einzelnen Tests nur den entsprechenden Setup()-Code für die Methoden aufrufen, die für das Mocking benötigt werden. Auf diese Weise erhalten Sie nur eine new PersonController(...) Anweisung pro Testklasse.
  • Wenn Sie viele Domänen-/Datenobjekte erstellen müssen, ist es hilfreich, Builder-Objekte zu erstellen, die zum Testen mit vernünftigen Werten beginnen. Anstatt also einen riesigen Konstruktor über den gesamten Code hinweg aufzurufen, mit einem Haufen falscher Werte, rufen Sie meistens nur an, z. B. var person = new PersonBuilder().Build(), möglicherweise mit ein paar verketteten Methodenaufrufen für Daten, die Ihnen in diesem Test besonders wichtig sind . Sie könnten auch interessiert sein an AutoFixture, aber ich habe es nie verwendet, so kann ich nicht dafür bürgen.

Wenn Sie Integration Tests schreiben, in dem Sie die Interaktion zwischen verschiedenen Teilen des Systems testen müssen, aber Sie müssen noch in der Lage sein, bestimmte Stücke zu verspotten, sollten Sie für Ihre Dienste Builder Erstellen von Klassen, so können Sie sagen, zB var personController = new PersonControllerBuilder.WithRealDatabase(connection).WithAuthorization(new AllowAllAuthorizationService()).Build().

Wenn Sie End-to-End- oder "Szenario" -Tests schreiben, bei denen Sie das gesamte System testen müssen, ist es sinnvoll, Ihr DI-Framework einzurichten und den gleichen Konfigurationscode wie für Ihr echtes Produkt zu verwenden Verwendet. Sie können die Konfiguration geringfügig ändern, um eine bessere programmatische Kontrolle über Dinge wie den angemeldeten Benutzer und dergleichen zu erhalten. Sie können auch die anderen Builder-Klassen verwenden, die Sie zum Erstellen von Daten erstellt haben.

var user = new PersonBuilder().Build(); 
using(Login.As(user)) 
{ 
    var controller = Container.Get<PersonController>(); 
    var result = controller.GetCurrentUser(); 
    Assert.AreEqual(result.Username, user.Username) 
} 
+1

Ich glaube, dass [Objekt Mutter] (http://xunitpatterns.com/Test%20Helper.html) ist der Name des Builder-Muster im Falle von Unit-Tests. Schöne Antwort übrigens. +1 – Steven

+0

Gegenüber Antwort auf: https://softwareengineering.stackexchange.com/questions/140992/is-dependency-injection-essential-for-unit-testing – sotn

+0

@sotn: Das scheint nicht die gleiche Frage zu sein, und Ich sehe dort keine Antworten, die dem zu widersprechen scheinen. Kannst du direkt mit der Antwort, über die du sprichst, verlinken und erklären, wie es "entgegengesetzt" ist? – StriplingWarrior

6

Verwenden Sie Ihren DI-Container nicht in Ihren Komponententests. In Komponententests versuchen Sie, eine Klasse oder ein Modul isoliert zu testen, und ein DI-Container in diesem Bereich ist wenig nützlich.

Beim Integrationstest ist das anders, da Sie testen möchten, wie die Komponenten in Ihrem System zusammenarbeiten und zusammenarbeiten. In diesem Fall verwenden Sie häufig Ihre Produktions-DI-Konfiguration und tauschen einige Ihrer Dienste gegen gefälschte Dienste aus (z. B. Ihre MailService), bleiben aber so nah wie möglich an der realen Sache.In diesem Fall verwenden Sie Ihren Container, um das gesamte Objektdiagramm aufzulösen.

Der Wunsch, einen DI-Container auch in den Komponententests zu verwenden, resultiert oft aus ineffektiven Mustern. Wenn Sie beispielsweise versuchen, die getestete Klasse mit all ihren Abhängigkeiten in jedem Test zu erstellen, erhalten Sie viel doppelten Initialisierungscode, und eine kleine Änderung in Ihrer getesteten Klasse kann in diesem Fall das System durchdringen und Sie dazu zwingen Ändern Sie Dutzende von Unit-Tests. Dies verursacht offensichtlich Wartungsprobleme.

Ein Muster, das mir in der Vergangenheit sehr geholfen hat, ist die Verwendung einer einfachen Testklassen-spezifischen Fabrikmethode. Diese Methode zentralisiert die Erstellung der zu testenden Klasse und minimiert die Anzahl der Änderungen, die durchgeführt werden müssen, wenn sich die Abhängigkeiten der getesteten Klasse ändern. Dies ist, wie eine solche Fabrik Methode aussehen könnte:

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()); 
} 

Diese Factory-Methode enthält eine params Array, das irgendeine Abhängigkeit akzeptiert. Der Code extrahiert die Abhängigkeiten von der Liste und falls eine bestimmte Abhängigkeit fehlt, wird eine neue falsche Implementierung eingefügt.

Dies funktioniert, da Sie in den meisten Tests nur an einer oder zwei Abhängigkeiten interessiert sind. Die anderen Abhängigkeiten sind erforderlich, damit die Klasse funktioniert, sind aber für den spezifischen Test nicht interessant. Mit der Factory-Methode können Sie also nur die Abhängigkeiten liefern, die für den Test interessant sind, während Sie das Rauschen ungenutzter Abhängigkeiten entfernen. Die Factory-Methode daher können Sie den folgenden Test schreiben:

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

    var cut = CreateValidClassUnderTest(logger); 

    // Act 
    cut.DoSomething(); 

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

Wenn Sie interessiert sind zu lernen, wie Lesbare Trustworthy und wartbare Tests zu schreiben, ich beraten Sie Roy Osherove Buch The Art of Unit Testing (zweite Ausgabe lesen). Das hat mir enorm geholfen.