So vermeiden Sie die Selbstnutzung von Klassen

9

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.

Problem

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.

Lösung

Die Lösung bestand darin, die zu testende Klasse und den Stub deleteUser method auszuspionieren:

%Vor%

Neues Problem

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?

    
ahmehri 16.07.2015, 14:08
quelle

4 Antworten

2

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.)

    
Jeff Bowman 18.07.2015 00:40
quelle
0

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.

%Vor%

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.

%Vor%

Und hier ist das aktualisierte MyClass

%Vor%     
dkatzel 16.07.2015 15:40
quelle
0

Spioniere die zu testende Klasse nicht aus, erweitere sie und überschreibe deleteUser, um etwas Gutes zu tun - oder zumindest etwas unter der Kontrolle deines Tests.

    
Bill K 16.07.2015 16:06
quelle
0
%Vor%

Jetzt kommt diese Lösung über die Methode der Außerkraftsetzung selbst

    
user1463488 20.03.2018 07:50
quelle