Ich habe die folgende Klasse:
%Vor% Diese Klasse stellt insofern eine Eigennutzung dar, als ihre öffentliche Methode deleteOrganization
ihre andere öffentliche Methode deleteUser
verwendet. In meinem Fall ist diese Klasse ein Legacy-Code, mit dem ich Unit-Tests hinzugefügt habe. Also habe ich mit einem Unit-Test gegen die erste Methode deleteOrganization
begonnen und herausgefunden, dass dieser Test erweitert wurde, um auch die Methode deleteUser
zu testen.
Das Problem ist, dass dieser Test nicht mehr isoliert ist (er sollte nur die Methode deleteOrganization
testen). Ich war verpflichtet, die verschiedenen Bedingungen in Bezug auf die deleteUser
-Methode zu behandeln, um sie zu bestehen, um den Test zu bestehen, was die Komplexität des Tests enorm erhöht hat.
Die Lösung bestand darin, die zu testende Klasse und den Stub deleteUser
method auszuspionieren:
Obwohl die vorherige Lösung das Problem löst, wird dies nicht empfohlen, da das Javadoc der Methode spy
Folgendes angibt:
Wie üblich werden Sie die partielle Scheinwarnung lesen: Objekt Englisch: www.dlr.de/en/desktopdefault.aspx/t...4_read-2938/ Die Programmierung orientiert sich weniger an der Komplexität, indem die Komplexität in separate, spezifische SRPy-Objekte. Wie ist partiell Schein passen in dieses Paradigma? Nun, es ist einfach nicht ... Partielle Spott bedeutet normalerweise, dass die Komplexität auf eine andere Methode verschoben wurde auf demselben Objekt. In den meisten Fällen ist das nicht so, wie Sie es möchten Entwerfen Sie Ihre Anwendung.
Die Komplexität der deleteOrganization
-Methode wurde in die deleteUser
-Methode verschoben und dies wird durch die Klassenselbstverwendung verursacht. Die Tatsache, dass diese Lösung nicht zusätzlich zur In most cases, this is not the way you want to design your application
-Anweisung empfohlen wird, legt nahe, dass es einen Code-Geruch gibt, und Refactoring ist in der Tat erforderlich, um diesen Code zu verbessern.
Wie kann ich diese Selbstnutzung entfernen? Gibt es ein Entwurfsmuster oder eine Refactoring-Technik, die angewendet werden kann?
Selbstbenutzung der Klasse ist nicht unbedingt ein Problem: Ich bin noch nicht überzeugt, dass "es die deleteOrganization-Methode nur aus irgendeinem anderen Grund als dem persönlichen oder Team-Stil testen sollte. Obwohl es hilfreich ist, deleteUser
und deleteOrganization
in unabhängigen und isolierten Einheiten zu belassen, ist dies nicht immer möglich oder praktisch - insbesondere, wenn die Methoden sich gegenseitig aufrufen oder sich auf einen gemeinsamen Status verlassen. Der Test besteht darin, die "kleinste testbare Einheit" zu testen - was nicht bedeutet, dass Methoden unabhängig sind oder dass sie unabhängig voneinander getestet werden können.
Sie haben einige Möglichkeiten, abhängig von Ihren Bedürfnissen und wie Sie erwarten, dass sich Ihre Codebasis weiterentwickelt. Zwei davon stammen aus deiner Frage, aber ich wiederhole sie unten mit Vorteilen.
Testen Sie die Blackbox.
Wenn Sie MyClass als undurchsichtige Oberfläche behandeln würden, würden Sie wahrscheinlich nicht erwarten oder verlangen, dass deleteOrganization
wiederholt deleteUser
aufruft, und Sie können sich vorstellen, dass sich die Implementierung so ändert, dass sie dies nicht tut. (Vielleicht würde ein zukünftiges Upgrade einen Datenbanktrigger haben, der zum Beispiel ein kaskadierendes Löschen vornimmt, oder eine einzelne Dateilöschung oder ein API-Aufruf könnte sich um das Löschen Ihres Unternehmens kümmern.)
Wenn Sie den Aufruf von deleteOrganization
an deleteUser
als privaten Methodenaufruf behandeln, dann testen Sie den MyClass-Vertrag und nicht seine Implementierung: Erstellen Sie eine Organisation mit einigen Benutzern, rufen Sie die Methode auf und überprüfen Sie, ob Organisation ist weg und das sind die Benutzer auch. Es kann zwar ausführlich sein, aber es ist der korrekteste und flexibelste Test.
Dies kann eine attraktive Option sein, wenn Sie erwarten, dass sich Ihre MyClass drastisch ändert oder eine ganz neue Ersatzimplementierung erhält.
Teilen Sie die Klasse horizontal in OrganizationDeleter und UserDeleter.
Um die Klasse zu "SRPy" zu machen (dh besser zum Grundsatz der einfachen Verantwortlichkeit ), wie die Mockito-Dokumentation weist darauf hin, dass deleteUser
und deleteOrganization
unabhängig sind. Indem Sie diese in zwei verschiedene Klassen aufteilen, können Sie den OrganizationDeleter einen falschen UserDeleter akzeptieren lassen, was die Notwendigkeit eines partiellen Mocks beseitigt.
Dies kann eine attraktive Option sein, wenn Sie erwarten, dass die Benutzerlogik abweicht oder Sie eine andere UserDeleter-Implementierung schreiben möchten.
Teilen Sie die Klasse vertikal in MyClass und MyClassService / MyClassHelper.
Wenn zwischen der Infrastruktur auf niedriger Ebene und den Aufrufen auf hoher Ebene genügend Unterschiede bestehen, möchten Sie sie möglicherweise trennen. MyClass würde deleteUser
und deleteOrganization
beibehalten, die erforderlichen Vorbereitungs- oder Validierungsschritte ausführen und dann einige Aufrufe an die Grundelemente in MyClassService vornehmen. deleteUser
wäre wahrscheinlich ein einfacher Delegat, wohingegen deleteOrganization
den Dienst aufrufen könnte, ohne seinen Nachbarn aufzurufen.
Wenn Sie genug Low-Level-Calls haben, um diese zusätzliche Trennung zu rechtfertigen, oder wenn MyClass sowohl Probleme auf hoher als auch auf niedriger Ebene behandelt, kann dies eine attraktive Option sein - besonders wenn es sich um einen Refactor handelt, der Ihnen schon einmal in den Sinn gekommen ist. Achten Sie jedoch darauf, das Muster des Baklava-Codes zu vermeiden (zu viele dünne, durchlässige Schichten, als Sie jemals halten könnten) Spur von).
Verwenden Sie weiterhin partielle Mocks.
Obwohl dies das Implementierungsdetail von internen Anrufen verliert, und dies gegen die Mockito-Warnung spricht, könnten unter Umständen partielle Mocks die beste pragmatische Wahl bieten. Wenn Sie eine Methode haben, die ihren Geschwister mehrmals aufruft, ist es möglicherweise nicht gut definiert, ob die innere Methode ein Helfer oder ein Peer ist, und ein partieller Schein würde Sie davor bewahren, diese Bezeichnung zu erstellen.
Dies kann eine attraktive Option sein, wenn Ihr Code ein Ablaufdatum hat oder experimentell genug ist, um ein vollständiges Design zu gewährleisten.
(Randnotiz: Es ist wahr, dass, solange MyClass nicht final ist, es offen für Unterklassen ist, was Teil davon ist, dass partielle Mocks möglich sind. Wenn Sie dokumentieren würden, dass der allgemeine Vertrag von deleteOrganization
mehrere Aufrufe an deleteUser
involvierte , dann wäre es völlig fair, eine Unterklasse oder einen partiellen Schein zu erstellen. Wenn es nicht dokumentiert ist, dann ist es ein Implementierungsdetail und sollte als solches behandelt werden.)
Sagt der Vertrag für deleteOrganization()
, dass alle User
s in dieser Organisation ebenfalls gelöscht werden?
Wenn dies der Fall ist, müssen Sie mindestens einen Teil der Lösch-Benutzerlogik in deleteOrganization()
beibehalten, da Clients Ihrer Klasse sich möglicherweise auf diese Funktion verlassen.
Bevor ich auf die Optionen eingehe, werde ich auch darauf hinweisen, dass jede dieser Löschmethoden public
ist und die Klasse und die Methoden beide nicht-final sind. Dadurch kann jemand die Klasse erweitern und die Methode überschreiben, was gefährlich sein könnte.
Lösung 1 - Organisation löschen muss noch Benutzer löschen
Wenn ich meinen Kommentar zu den übergeordneten Gefahren in Betracht ziehe, ziehen wir den Teil heraus, der Benutzer tatsächlich aus deleteUser()
löscht. Ich nehme an, dass die öffentliche Methode deleteUser
eine zusätzliche Validierung und möglicherweise andere Geschäftslogik durchführt, die von deleteOrganization()
nicht benötigt wird.
Dies verwendet den tatsächlichen Code, der die Löschung durchführt, und vermeidet die Gefahr, dass sich Unterklassen deleteUser()
anders verhalten.
Lösung 2 - Benutzer müssen nicht innerhalb der deleteOrganization () -Methode
gelöscht werden Wenn wir die Benutzer nicht ständig aus der Organisation löschen müssen, können wir diesen Teil des Codes aus deleteOrganization () entfernen. An den wenigen Stellen, wo wir die Benutzer auch löschen müssen, können wir einfach die Schleife wie deleteOrganization jetzt machen. Da es öffentliche Methoden verwendet, kann nur jeder Client sie aufrufen. Wir können die Logik auch in eine Service
-Klasse einfügen.
Und hier ist das aktualisierte MyClass
Jetzt kommt diese Lösung über die Methode der Außerkraftsetzung selbst
Tags und Links java unit-testing design-patterns mocking mockito