Ich habe darüber nachgedacht, wie man testbares Design mithilfe der Abhängigkeitsinjektion mit der Bereitstellung einer einfachen festen öffentlichen API ausbalanciert. Mein Dilemma ist: Die Leute möchten so etwas tun var server = new Server(){ ... }
und müssen sich nicht darum kümmern, die vielen Abhängigkeiten und die Abhängigkeitsgraphen zu erstellen, die ein Mensch haben Server(,,,,,,)
kann. Während der Entwicklung mache ich mir keine allzu großen Sorgen, da ich ein IoC / DI-Framework verwende, um all das zu handhaben (ich verwende nicht die Aspekte des Lebenszyklusmanagements eines Containers, die die Dinge weiter komplizieren würden).
Jetzt ist es unwahrscheinlich, dass die Abhängigkeiten erneut implementiert werden. In diesem Fall dient die Komponentierung fast ausschließlich der Testbarkeit (und einem anständigen Design!), Anstatt Nähte für Erweiterungen usw. zu erstellen. In 99,999% der Fälle möchten Benutzer eine Standardkonfiguration verwenden. So. Ich könnte die Abhängigkeiten fest codieren. Will das nicht, wir verlieren unsere Tests! Ich könnte einen Standardkonstruktor mit fest codierten Abhängigkeiten und einen, der Abhängigkeiten akzeptiert, bereitstellen. Das ist ... chaotisch und wahrscheinlich verwirrend, aber machbar. Ich könnte den abhängigkeitsempfangenden Konstruktor intern machen und meine Unit-Tests zu einer Friend-Assembly machen (unter der Annahme von C #), die die öffentliche API aufräumt, aber eine unangenehme versteckte Falle zur Wartung zurücklässt. Zwei Konstruktoren zu haben, die implizit und nicht explizit miteinander verbunden sind, wäre in meinem Buch im Allgemeinen ein schlechtes Design.
Im Moment ist das ungefähr das geringste Übel, an das ich denken kann. Meinungen? Weisheit?
HttpListener
Konstruktor ändert,Server
muss sich auch die Klasse ändern. Sie sind gekoppelt. Eine bessere Lösung ist das, was @Winston Ewert weiter unten sagt, nämlich die Bereitstellung einer Standardklasse mit vordefinierten Abhängigkeiten außerhalb der API der Bibliothek. Dann ist der Programmierer nicht gezwungen, alle Abhängigkeiten einzurichten, da Sie die Entscheidung für ihn treffen, aber er hat immer noch die Flexibilität, sie später zu ändern. Dies ist eine große Sache, wenn Sie Abhängigkeiten von Drittanbietern haben.In Java ist es in solchen Fällen üblich, dass eine "Standard" -Konfiguration von einer Fabrik erstellt wird, die unabhängig von dem tatsächlichen "ordnungsgemäß" verhaltenen Server ist. Ihr Benutzer schreibt also:
Der
Server
Konstruktor des Moduls akzeptiert weiterhin alle Abhängigkeiten und kann für eine umfassende Anpassung verwendet werden.quelle
Wie wäre es mit einer Unterklasse, die die "öffentliche API" liefert?
Der Benutzer kann
new StandardServer()
und ist unterwegs. Sie können auch die Server-Basisklasse verwenden, wenn Sie mehr Kontrolle über die Funktionsweise des Servers wünschen. Dies ist mehr oder weniger der Ansatz, den ich benutze. (Ich verwende kein Framework, da ich den Punkt noch nicht gesehen habe.)Dies macht die interne API immer noch verfügbar, aber ich denke, Sie sollten dies tun. Sie haben Ihre Objekte in verschiedene nützliche Komponenten aufgeteilt, die unabhängig voneinander funktionieren sollen. Es ist nicht abzusehen, wann ein Dritter eine der Komponenten isoliert verwenden möchte.
quelle
Das scheint mir keine "böse versteckte Falle" zu sein. Der öffentliche Konstruktor sollte nur den internen mit den "Standard" -Abhängigkeiten aufrufen. Solange klar ist, dass der öffentliche Konstruktor nicht geändert werden sollte, sollte alles in Ordnung sein.
quelle
Ich stimme Ihrer Meinung voll und ganz zu. Wir möchten die Grenzen der Komponentennutzung nicht nur zum Zweck des Komponententests verschmutzen. Dies ist das gesamte Problem der DI-basierten Testlösung.
Ich würde vorschlagen, InjectableFactory / InjectableInstance- Muster zu verwenden. InjectableInstance sind einfache wiederverwendbare Dienstprogrammklassen, die einen veränderlichen Wert besitzen . Die Komponentenschnittstelle enthält einen Verweis auf diesen Werthalter, der mit der Standardimplementierung initialisiert wurde. Die Schnittstelle stellt eine Singleton-Methode get () bereit , die an den Werthalter delegiert. Der Komponentenclient ruft die Methode get () anstelle von new auf, um eine Implementierungsinstanz abzurufen und direkte Abhängigkeiten zu vermeiden. Während der Testzeit können wir optional die Standardimplementierung durch einen Schein ersetzen. Im Folgenden werde ich ein Beispiel TimeProvider verwendenKlasse, die eine Abstraktion von Date () ist, sodass Unit-Tests injizieren und verspotten können, um verschiedene Momente zu simulieren. Entschuldigung, ich werde Java hier verwenden, da ich mit Java besser vertraut bin. C # sollte ähnlich sein.
Die InjectableInstance ist ziemlich einfach zu implementieren, ich habe eine Referenzimplementierung in Java. Weitere Informationen finden Sie in meinem Blogbeitrag Abhängigkeitsrichtlinie mit Injectable Factory
quelle