Verwendung des DI-Containers in Komponententests

8

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 Simple Injector so konfiguriert, dass alles ausgefüllt wird, und alles ist prächtig.

Wir haben jedoch nicht den einfachen Injektor 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 Unit-Tests zu reparieren.

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

Hat jemand eine Methode, um diese zu verwalten?

    
Jeff Dege 15.09.2015, 20:13
quelle

2 Antworten

12

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

  • Wenn Sie feststellen, dass Sie eine Menge repetitiven Code für new haben, der eine Instanz Ihrer Klasse mit allen von Ihnen erstellten Mocks aufbaut, ist eine nützliche Strategie, alle Ihre Mocks zu erstellen und die Instanz für die zu testender 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 er zum Nachahmen benötigt. 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 mit vernünftigen Werten zum Testen beginnen. Anstatt also einen riesigen Konstruktor über den gesamten Code hinweg aufzurufen, rufen Sie mit einem Haufen falscher Werte meistens nur an, zB var person = new PersonBuilder().Build() , möglicherweise mit ein paar Kettenanrufen für Daten, auf die Sie sich besonders konzentrieren dieser Test. Sie könnten auch interessiert sein an AutoFixture , aber ich habe es nie benutzt, damit ich nicht dafür bürgen kann.

Wenn Sie Integration -Tests schreiben, bei denen Sie die Interaktion zwischen verschiedenen Teilen des Systems testen müssen, Sie aber trotzdem bestimmte Teile vortäuschen können, sollten Sie in Erwägung ziehen, Builder-Klassen für Ihr System zu erstellen Dienstleistungen, 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, indem Sie denselben Konfigurationscode verwenden, den Ihr reales Produkt 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 weiterhin die anderen Builder-Klassen verwenden, die Sie zum Erstellen von Daten erstellt haben.

%Vor%     
StriplingWarrior 15.09.2015 20:32
quelle
6

Verwenden Sie Ihren DI-Container nicht in Ihren Komponententests. In Komponententests versuchen Sie, eine Klasse oder ein Modul isoliert zu testen, und für einen DI-Container in diesem Bereich ist dies wenig sinnvoll.

Beim Integrationstest sieht das anders aus, denn Sie möchten testen, wie die Komponenten in Ihrem System zusammenarbeiten und zusammenarbeiten. In diesem Fall verwenden Sie häufig Ihre DI-Produktionskonfiguration 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, rührt oft von ineffektiven Mustern her. 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 führt offensichtlich zu Problemen bei der Wartbarkeit.

Ein Muster, das mir in der Vergangenheit sehr geholfen hat, ist die Verwendung einer einfachen Test-SUT-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. So könnte eine solche Fabrikmethode aussehen:

%Vor%

Diese Factory-Methode dupliziert die Konstruktorargumente der Klasse, macht sie jedoch alle optional. Für jede bestimmte nicht angegebene Abhängigkeit wird eine neue falsche Implementierung eingefügt.

Das funktioniert sehr gut, da Sie sich bei den meisten Tests nur für ein oder zwei Abhängigkeiten interessieren. Die anderen Abhängigkeiten sind möglicherweise erforderlich, damit die Klasse funktioniert, sind aber für diesen speziellen 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 ermöglicht Ihnen daher, den folgenden Test zu schreiben:

%Vor%

Wenn Sie lernen möchten, wie man lesbar, vertrauenswürdig und wartbar schreibt ( RTM < Ich empfehle Ihnen, Roy Osheroves Buch The Art of Unit Testing (zweite Ausgabe) zu lesen. Das hat mir enorm geholfen.

    
Steven 15.09.2015 20:36
quelle