Ich habe heute Morgen einige Blog-Beiträge gelesen und bin über diesen gestolpert :
Wenn die einzige Klasse, die jemals die Kundenschnittstelle implementiert, CustomerImpl ist, haben Sie nicht wirklich Polymorphismus und Substituierbarkeit, da es in der Praxis zur Laufzeit nichts zu ersetzen gibt. Es ist eine falsche Allgemeinheit.
Das macht für mich Sinn, da die Implementierung einer Schnittstelle die Komplexität erhöht und wenn es nur eine Implementierung gibt, könnte man argumentieren, dass sie unnötige Komplexität hinzufügt. Das Schreiben von Code, der abstrakter ist als er sein muss, wird oft als Codegeruch angesehen, der als "spekulative Allgemeinheit" bezeichnet wird (auch im Beitrag erwähnt).
Wenn ich jedoch TDD folge, kann ich ohne diese spekulative Allgemeinheit keine Testdoppel (einfach) erstellen, sei es in Form einer Schnittstellenimplementierung oder unserer anderen polymorphen Option, wodurch die Klasse vererbbar und ihre Methoden virtuell werden.
Wie bringen wir diesen Kompromiss in Einklang? Lohnt es sich, spekulativ allgemein zu sein, um das Testen / TDD zu erleichtern? Wenn Sie Test-Doubles verwenden, zählen diese als zweite Implementierungen und machen die Allgemeinheit somit nicht mehr spekulativ? Sollten Sie ein schwereres Mocking-Framework in Betracht ziehen, das das Verspotten konkreter Mitarbeiter ermöglicht (z. B. Moles versus Moq in der C # -Welt)? Oder sollten Sie mit den konkreten Klassen testen und so etwas wie "Integrationstests" schreiben, bis Ihr Design natürlich Polymorphismus erfordert?
Ich bin gespannt auf die Ansichten anderer Leute zu diesem Thema - vielen Dank im Voraus für Ihre Meinung.
quelle
Antworten:
Ich habe den Blog-Beitrag gelesen und bin mit vielen Aussagen des Autors einverstanden. Wenn Sie jedoch sind Ihre Schreiben von Code - Schnittstellen für Einheit Testzwecke verwenden, würde ich sagen , dass die Pseudo-Implementierung der Schnittstelle ist Ihre zweite Implementierung. Ich würde argumentieren, dass es Ihrem Code wirklich nicht viel an Komplexität hinzufügt, insbesondere wenn der Kompromiss, dies nicht zu tun, dazu führt, dass Ihre Klassen eng miteinander verbunden und schwer zu testen sind.
quelle
Stream
Klasse, aber keine enge Kopplung.Das Testen von Code im Allgemeinen ist nicht einfach. Wenn es so wäre, hätten wir das alles schon vor langer Zeit gemacht und erst in den letzten 10 bis 15 Jahren so viel daraus gemacht. Eine der größten Schwierigkeiten bestand immer darin, zu bestimmen, wie Code getestet werden soll, der zusammenhängend, gut faktorisiert und testbar ist, ohne die Kapselung zu unterbrechen. Das BDD-Prinzip schlägt vor, dass wir uns fast ausschließlich auf das Verhalten konzentrieren, und scheint in gewisser Weise darauf hinzudeuten, dass Sie sich nicht wirklich so sehr um die inneren Details kümmern müssen, aber dies kann es oft schwierig machen, die Dinge zu testen, wenn es welche gibt Viele private Methoden, die auf sehr versteckte Weise "Sachen" machen, da dies die Gesamtkomplexität Ihres Tests erhöhen kann, um alle möglichen Ergebnisse auf einer öffentlicheren Ebene zu behandeln.
Spott kann bis zu einem gewissen Grad helfen, ist aber wiederum ziemlich extern ausgerichtet. Die Abhängigkeitsinjektion kann auch sehr gut funktionieren, wiederum mit Mocks oder Test-Doubles. Dies kann jedoch auch erfordern, dass Sie Elemente entweder über eine Schnittstelle oder direkt verfügbar machen, die Sie sonst möglicherweise lieber versteckt hätten - dies gilt insbesondere, wenn Sie dies wünschen Haben Sie ein gutes paranoides Sicherheitsniveau für bestimmte Klassen in Ihrem System.
Für mich ist die Jury immer noch unschlüssig, ob Sie Ihre Klassen so gestalten sollen, dass sie leichter zu testen sind. Dies kann zu Problemen führen, wenn Sie neue Tests bereitstellen müssen, während der Legacy-Code beibehalten wird. Ich akzeptiere, dass Sie in der Lage sein sollten, absolut alles in einem System zu testen, aber ich mag es nicht, die privaten Interna einer Klasse - auch indirekt - freizulegen, nur damit ich einen Test für sie schreiben kann.
Für mich war die Lösung immer, einen ziemlich pragmatischen Ansatz zu wählen und eine Reihe von Techniken zu kombinieren, um sie an die jeweilige Situation anzupassen. Ich verwende viele geerbte Testdoppel, um innere Eigenschaften und Verhaltensweisen für meine Tests aufzudecken. Ich verspotte alles, was an meine Klassen angehängt werden kann, und wo dies die Sicherheit meiner Klassen nicht beeinträchtigt, werde ich ein Mittel bereitstellen, um Verhaltensweisen zum Testen zu überschreiben oder zu injizieren. Ich werde sogar in Betracht ziehen, eine ereignisgesteuerte Schnittstelle bereitzustellen, wenn dies dazu beiträgt, die Fähigkeit zum Testen von Code zu verbessern
Wo ich einen "nicht testbaren" Code finde , schaue ich nach, ob ich ihn umgestalten kann, um die Dinge testbarer zu machen. Wo viel privater Code hinter den Kulissen versteckt ist, finden Sie oft neue Klassen, die darauf warten, ausgebrochen zu werden. Diese Klassen können intern verwendet werden, können jedoch häufig unabhängig voneinander mit weniger privatem Verhalten und anschließend häufig weniger Zugriffs- und Komplexitätsebenen getestet werden. Eine Sache, die ich jedoch vermeiden möchte, ist das Schreiben von Produktionscode mit integriertem Testcode. Es kann verlockend sein, " Testfahnen " zu erstellen
if testing then ...
, die dazu führen, dass solche Schrecken eingeschlossen werden , die auf ein Testproblem hinweisen, das nicht vollständig dekonstruiert und unvollständig gelöst ist.Es könnte hilfreich sein , das Buch xUnit Test Patterns von Gerard Meszaros zu lesen , in dem all diese Dinge ausführlicher behandelt werden, als ich hier behandeln kann. Ich mache wahrscheinlich nicht alles, was er vorschlägt, aber es hilft, einige der schwierigeren Testsituationen zu klären. Letztendlich möchten Sie in der Lage sein, Ihre Testanforderungen zu erfüllen, während Sie weiterhin Ihre bevorzugten Designs anwenden, und es hilft, alle Optionen besser zu verstehen, um besser entscheiden zu können, wo Sie möglicherweise Kompromisse eingehen müssen.
quelle
Hat die von Ihnen verwendete Sprache eine Möglichkeit, ein Objekt zum Testen zu "verspotten"? Wenn ja, können diese lästigen Schnittstellen verschwinden.
Aus einem anderen Grund kann es Gründe geben, ein SimpleInterface und ein einziges ComplexThing zu haben, das es implementiert. Es kann Teile des ComplexThing geben, auf die der SimpleInterface-Benutzer nicht zugreifen möchte. Es liegt nicht immer an einem überbordenden OO-ish-Codierer.
Ich werde jetzt zurücktreten und alle auf die Tatsache springen lassen, dass Code, der dies tut, für sie "schlecht riecht".
quelle
Ich werde in zwei Teilen antworten:
Sie benötigen keine Schnittstellen, wenn Sie nur am Testen interessiert sind. Zu diesem Zweck verwende ich Mocking-Frameworks (in Java: Mockito oder easymock). Ich glaube, dass der von Ihnen entworfene Code nicht zu Testzwecken geändert werden sollte. Das Schreiben von testbarem Code entspricht dem Schreiben von modularem Code, daher neige ich dazu, modularen (testbaren) Code zu schreiben und nur die öffentlichen Schnittstellen des Codes zu testen.
Ich habe in einem großen Java-Projekt gearbeitet und bin zutiefst davon überzeugt, dass die Verwendung von schreibgeschützten (nur Getter-) Schnittstellen der richtige Weg ist (bitte beachten Sie, dass ich ein großer Fan von Unveränderlichkeit bin). Die implementierende Klasse verfügt möglicherweise über Setter, dies ist jedoch ein Implementierungsdetail, das keinen äußeren Schichten ausgesetzt werden sollte. Aus einer anderen Perspektive bevorzuge ich Komposition gegenüber Vererbung (Modularität, erinnerst du dich?), Daher helfen auch hier Schnittstellen. Ich bin bereit, die Kosten für spekulative Allgemeinheit zu bezahlen, als mich selbst in den Fuß zu schießen.
quelle
Ich habe viele Vorteile gesehen, seit ich angefangen habe, mehr auf eine Schnittstelle jenseits des Polymorphismus zu programmieren.
Viele Leute werden zustimmen, dass mehr, kleinere Klassen besser sind als weniger, größere Klassen. Sie müssen sich nicht so sehr auf einmal konzentrieren und jede Klasse hat einen genau definierten Zweck. Andere sagen vielleicht, dass Sie die Komplexität erhöhen, indem Sie mehr Klassen haben.
Es ist gut, Tools zu verwenden, um die Produktivität zu verbessern, aber ich denke, dass die Verwendung von Mock-Frameworks und dergleichen, anstatt Testbarkeit und Modularität direkt in den Code einzubauen, auf lange Sicht zu Code mit geringerer Qualität führen wird.
Alles in allem glaube ich, dass es mir geholfen hat, Code mit höherer Qualität zu schreiben, und die Vorteile überwiegen bei weitem alle Konsequenzen.
quelle