Ich lerne gerade TDD. Nach meinem Verständnis sind private Methoden nicht testbar und sollten nicht besorgt sein, da die öffentliche API genügend Informationen zur Überprüfung der Objektintegrität bereitstellt.
Ich habe OOP für eine Weile verstanden. Ich verstehe, dass private Methoden Objekte gekapselter machen und somit Änderungen und Fehlern widerstehen. Sie sollten daher standardmäßig verwendet werden und nur die für Clients wichtigen Methoden sollten veröffentlicht werden.
Nun, es ist mir möglich, ein Objekt zu erstellen, das nur über private Methoden verfügt und mit anderen Objekten interagiert, indem ich deren Ereignisse abhöre. Dies wäre sehr gekapselt, aber völlig unprüfbar.
Es wird auch als schlechte Praxis angesehen, Methoden zu Testzwecken hinzuzufügen.
Bedeutet dies, dass TDD im Widerspruch zur Kapselung steht? Was ist das richtige Gleichgewicht? Ich bin geneigt, die meisten oder alle meiner Methoden jetzt zu veröffentlichen ...
quelle
Antworten:
Ziehen Sie das Testen der Schnittstelle dem Testen der Implementierung vor.
Dies hängt von Ihrer Entwicklungsumgebung ab (siehe unten).
Richtig, TDD konzentriert sich auf das Testen der Schnittstelle.
Private Methoden sind ein Implementierungsdetail, das sich während eines Re-Faktor-Zyklus ändern kann. Es sollte möglich sein, Änderungen vorzunehmen, ohne die Benutzeroberfläche oder das Black-Box- Verhalten zu ändern . Tatsächlich ist dies Teil des Vorteils von TDD. Die Leichtigkeit, mit der Sie das Vertrauen erzeugen können, dass sich klasseninterne Änderungen ergeben, wirkt sich nicht auf Benutzer dieser Klasse aus.
Auch wenn die Klasse über keine öffentlichen Methoden verfügt, sind die Event-Handler die öffentlichen Schnittstellen , und sie richten sich gegen diese öffentlichen Schnittstellen , die Sie testen können.
Da die Ereignisse die Schnittstelle sind, müssen Sie diese Ereignisse generieren, um das Objekt zu testen.
Prüfen Sie, ob Sie Scheinobjekte als Klebstoff für Ihr Testsystem verwenden können. Es sollte möglich sein, ein einfaches Mock-Objekt zu erstellen, das ein Ereignis generiert und die resultierende Statusänderung aufnimmt (möglich durch ein anderes Empfänger-Mock-Objekt).
Auf jeden Fall sollten Sie sehr vorsichtig sein , wenn Sie den internen Zustand offenlegen .
Absolut nicht.
TDD sollte die Implementierung Ihrer Klassen nur ändern, um sie zu vereinfachen (indem Sie YAGNI von einem früheren Punkt aus anwenden ).
Best Practice mit TDD ist identisch mit Best Practice ohne TDD. Sie müssen nur herausfinden, warum dies früher der Fall ist, da Sie die Benutzeroberfläche während der Entwicklung verwenden.
Dies würde eher das Baby mit dem Badewasser werfen.
Sie sollten nicht müssen alle Methoden öffentlich zu machen, so dass Sie in einem TDD - Weise entwickeln können. Sehen Sie sich meine Notizen unten an, um zu sehen, ob Ihre privaten Methoden wirklich nicht testbar sind.
Ein detaillierterer Blick auf das Testen privater Methoden
Wenn Sie unbedingt ein privates Verhalten einer Klasse testen müssen , haben Sie je nach Sprache / Umgebung drei Möglichkeiten:
Offensichtlich ist die 3. Option bei weitem die beste.
1) Ordnen Sie die Tests der Klasse zu, die Sie testen möchten (nicht ideal).
Das Speichern von Testfällen in derselben Klasse / Quelldatei wie der zu testende Produktionscode ist die einfachste Option. Ohne viele Pre-Prozessor-Direktiven oder -Anmerkungen wird Ihr Testcode jedoch unnötigerweise aufgebläht, und je nachdem, wie Sie Ihren Code strukturiert haben, können Sie den Benutzern dieses Codes versehentlich die interne Implementierung zugänglich machen.
2) Machen Sie die privaten Methoden, die Sie testen möchten, als öffentliche Methoden verfügbar (wirklich keine gute Idee).
Wie bereits erwähnt, handelt es sich hierbei um eine sehr schlechte Praxis, die die Kapselung zerstört und die interne Implementierung für Benutzer des Codes verfügbar macht.
3) Verwenden Sie eine bessere Testumgebung (beste Option, falls verfügbar)
In der Eclipse-Welt kann 3. durch die Verwendung von Fragmenten erreicht werden . In der C # Welt könnten wir verwenden Teilklassen . Andere Sprachen / Umgebungen verfügen häufig über ähnliche Funktionen. Sie müssen sie nur finden.
Die blinde Annahme, dass 1. oder 2. die einzigen Optionen sind, würde wahrscheinlich dazu führen, dass die Produktionssoftware mit Testcode oder bösen Schnittstellen vollgestopft ist, die ihre schmutzige Wäsche in der Öffentlichkeit waschen. * 8 ')
quelle
Natürlich können Sie private Methoden haben und natürlich können Sie sie testen.
Entweder gibt es eine Möglichkeit, die private Methode zum Laufen zu bringen. In diesem Fall können Sie sie auf diese Weise testen, oder es gibt keine Möglichkeit , die private Methode zum Laufen zu bringen. In diesem Fall: Warum zum Teufel versuchen Sie es nur zu testen? lösche das verdammte Ding!
In deinem Beispiel:
Warum wäre das nicht testbar? Wenn die Methode als Reaktion auf ein Ereignis aufgerufen wird, muss der Test dem Objekt nur ein geeignetes Ereignis zuführen.
Es geht nicht darum, keine privaten Methoden zu haben, es geht darum, die Kapselung nicht zu brechen. Sie können über private Methoden verfügen, sollten diese jedoch über die öffentliche API testen. Wenn die öffentliche API auf Ereignissen basiert, verwenden Sie Ereignisse.
Für den allgemeineren Fall von privaten Hilfsmethoden können sie mit den öffentlichen Methoden getestet werden, die sie aufrufen. Insbesondere, da Sie nur Code schreiben dürfen, um einen fehlgeschlagenen Test zu bestehen, und Ihre Tests die öffentliche API testen, wird jeder neue Code, den Sie schreiben, normalerweise öffentlich sein. Private Methoden werden nur als Ergebnis eines Extract Method Refactorings angezeigt , wenn sie aus einer bereits vorhandenen öffentlichen Methode entfernt werden. In diesem Fall deckt der ursprüngliche Test, der die öffentliche Methode testet, auch die private Methode ab, da die öffentliche Methode die private Methode aufruft.
Daher erscheinen private Methoden in der Regel nur, wenn sie aus bereits getesteten öffentlichen Methoden extrahiert und somit auch bereits getestet wurden.
quelle
internal
Methoden oder öffentliche Methoden ininternal
Klassen sollten ziemlich oft direkt getestet werden. Glücklicherweise unterstützt .net dieInternalsVisibleToAttribute
, aber ohne sie wäre das Testen dieser Methode eine PITA.Wenn Sie eine neue Klasse in Ihrem Code erstellen, tun Sie dies, um einige Anforderungen zu erfüllen. Die Anforderungen legen fest, was der Code tun muss, nicht wie . Dies macht es leicht verständlich, warum die meisten Tests auf der Ebene öffentlicher Methoden stattfinden.
Durch Tests stellen wir sicher, dass der Code das tut, was erwartet wird, löst entsprechende Ausnahmen aus, wenn erwartet, usw. Es ist uns egal, wie der Code vom Entwickler implementiert wird. Die Implementierung, dh die Funktionsweise des Codes, ist uns egal. Es ist jedoch sinnvoll, private Methoden nicht zu testen.
Wenn Sie Klassen testen möchten, die keine öffentlichen Methoden haben und nur durch Ereignisse mit der Außenwelt interagieren, können Sie dies auch testen, indem Sie die Ereignisse durch Tests senden und die Antwort abhören. Wenn eine Klasse beispielsweise jedes Mal, wenn sie ein Ereignis empfängt, eine Protokolldatei speichern muss, sendet der Komponententest das Ereignis und überprüft, ob die Protokolldatei geschrieben wurde.
Last but not least ist es in einigen Fällen durchaus sinnvoll, private Methoden zu testen. Aus diesem Grund können Sie beispielsweise in .NET nicht nur öffentliche, sondern auch private Klassen testen, auch wenn die Lösung nicht so einfach ist wie bei öffentlichen Methoden.
quelle
Ich bin mit dieser Aussage nicht einverstanden, oder ich würde sagen, dass Sie private Methoden nicht direkt testen . Eine öffentliche Methode kann verschiedene private Methoden aufrufen. Vielleicht wollte der Autor "kleine" Methoden haben und extrahierte einen Teil des Codes in eine klug benannte private Methode.
Unabhängig davon, wie die öffentliche Methode geschrieben ist, sollte Ihr Testcode alle Pfade abdecken. Wenn Sie nach Ihren Tests feststellen, dass eine der Verzweigungsanweisungen (if / switch) in einer privaten Methode noch nie in Ihren Tests behandelt wurde, liegt ein Problem vor. Entweder haben Sie einen Fall verpasst und die Implementierung ist korrekt ODER die Implementierung ist falsch, und dieser Zweig hätte eigentlich nie existieren dürfen.
Deshalb benutze ich häufig Cobertura und NCover, um sicherzustellen, dass mein öffentlicher Methodentest auch private Methoden abdeckt. Fühlen Sie sich frei, gute OO-Objekte mit privaten Methoden zu schreiben, und lassen Sie TDD / Testing in solchen Angelegenheiten nicht zu.
quelle
Ihr Beispiel ist immer noch perfekt testbar, solange Sie Dependency Injection verwenden, um die Instanzen bereitzustellen, mit denen Ihr CUT interagiert. Dann können Sie einen Mock verwenden, die Ereignisse von Interesse generieren und dann beobachten, ob der CUT die richtigen Aktionen für seine Abhängigkeiten ausführt oder nicht.
Auf der anderen Seite können Sie, wenn Sie eine Sprache mit guter Veranstaltungsunterstützung haben, einen etwas anderen Weg einschlagen. Ich mag es nicht, wenn Objekte Ereignisse selbst abonnieren, sondern wenn die Factory, die das Objekt erstellt, Ereignisse mit den öffentlichen Methoden des Objekts verknüpft. Es ist einfacher zu testen und macht von außen sichtbar, für welche Arten von Ereignissen der CUT getestet werden muss.
quelle
Sie sollten nicht mit privaten Methoden aufgeben müssen. Es ist durchaus vernünftig, sie zu verwenden, aber aus Sicht der Tests ist es schwieriger, sie direkt zu testen, ohne die Kapselung zu beschädigen oder Ihren Klassen testspezifischen Code hinzuzufügen. Der Trick besteht darin, die Dinge zu minimieren, von denen Sie wissen, dass sie Ihren Darm winden lassen, weil Sie das Gefühl haben, Sie hätten Ihren Code verschmutzt.
Dies sind die Dinge, an die ich denke, um ein funktionierendes Gleichgewicht zu erreichen.
Quer denken. Halten Sie Ihre Klassen und Methoden klein und verwenden Sie viel Komposition. Es klingt nach mehr Arbeit, aber am Ende werden Sie mehr individuell testbare Objekte haben, Ihre Tests werden einfacher sein, Sie werden mehr Möglichkeiten haben, einfache Mocks anstelle von echten, großen und komplexen Objekten zu verwenden, hoffentlich gut. faktorisierter und lose gekoppelter Code, und was noch wichtiger ist, Sie geben sich selbst mehr Optionen. Wenn Sie die Dinge klein halten, können Sie am Ende Zeit sparen, da Sie die Anzahl der Dinge reduzieren, die Sie für jede Klasse einzeln überprüfen müssen, und Sie reduzieren auf natürliche Weise die Code-Spaghetti, die manchmal auftreten können, wenn eine Klasse groß wird und viele hat Internes Verhalten von voneinander abhängigem Code.
quelle
Wie reagiert dieses Objekt auf diese Ereignisse? Vermutlich muss es Methoden für andere Objekte aufrufen. Sie können es testen, indem Sie prüfen, ob diese Methoden aufgerufen werden. Lassen Sie es ein Scheinobjekt aufrufen, und dann können Sie leicht behaupten, dass es das tut, was Sie erwarten.
Das Problem ist, dass wir nur die Interaktion des Objekts mit anderen Objekten testen möchten. Es ist uns egal, was in einem Objekt vor sich geht. Also nein, Sie sollten keine öffentlichen Methoden mehr haben als zuvor.
quelle
Ich habe auch mit dem gleichen Problem zu kämpfen. Wirklich, der Weg, um es zu umgehen, ist folgender: Wie erwarten Sie, dass der Rest Ihres Programms mit dieser Klasse zusammenarbeitet? Testen Sie Ihre Klasse entsprechend. Dies zwingt Sie dazu, Ihre Klasse basierend auf der Art und Weise zu gestalten, wie der Rest des Programms mit ihr in Verbindung steht, und fördert in der Tat die Kapselung und das gute Design Ihrer Klasse.
quelle
Anstelle der privaten Verwendung Standardmodifikator. Dann können Sie diese Methoden einzeln testen, nicht nur in Verbindung mit öffentlichen Methoden. Dies setzt voraus, dass Ihre Tests dieselbe Paketstruktur haben wie Ihr Hauptcode.
quelle
internal
in .net.Einige private Methoden sind normalerweise kein Problem. Sie testen sie einfach über die öffentliche API, als ob der Code in Ihre öffentlichen Methoden eingebunden wäre. Ein Überschuss an privaten Methoden kann ein Zeichen für einen schlechten Zusammenhalt sein. Ihre Klasse sollte eine zusammenhängende Verantwortung haben, und oft machen die Leute Methoden privat, um den Anschein von Zusammenhalt zu erwecken, wo keiner wirklich existiert.
Möglicherweise verfügen Sie über einen Ereignishandler, der als Reaktion auf diese Ereignisse viele Datenbankaufrufe ausführt. Da es offensichtlich eine schlechte Praxis ist, einen Ereignishandler zu instanziieren, um Datenbankaufrufe durchzuführen, besteht die Versuchung darin, alle datenbankbezogenen Aufrufe als private Methoden zu definieren, wenn sie wirklich in eine separate Klasse gezogen werden sollten.
quelle
TDD widerspricht nicht der Verkapselung. Nehmen Sie das einfachste Beispiel für eine Getter-Methode oder -Eigenschaft, je nachdem, welche Sprache Sie gewählt haben. Angenommen, ich habe ein Kundenobjekt und möchte, dass es ein ID-Feld enthält. Der erste Test, den ich schreiben werde, lautet "customer_id_initializes_to_zero". Ich definiere den Getter, um eine nicht implementierte Ausnahme auszulösen und zu beobachten, wie der Test fehlschlägt. Das Einfachste, was ich tun kann, um diesen Test zu bestehen, ist, den Getter auf Null zu setzen.
Von dort aus gehe ich zu anderen Tests über, bei denen die Kunden-ID vermutlich ein tatsächliches Funktionsfeld darstellt. Irgendwann muss ich wahrscheinlich ein privates Feld erstellen, das die Kundenklasse verwendet, um zu verfolgen, was vom Getter zurückgegeben werden soll. Wie genau verfolge ich das? Ist es ein einfaches Backing Int? Behalte ich einen String im Auge und konvertiere ihn dann in int? Behalte ich 20 Zoll im Auge und mittle sie? Die Außenwelt kümmert sich nicht darum - und Ihre TDD-Tests kümmern sich nicht darum. Das ist ein gekapseltes Detail.
Ich denke, dass dies beim Starten von TDD nicht immer sofort offensichtlich ist - Sie testen nicht, was Methoden intern tun - Sie testen weniger detaillierte Bedenken der Klasse. Sie möchten also nicht testen, ob diese Methode einen
DoSomethingToFoo()
Balken instanziiert, eine Methode darauf aufruft, zwei zu einer seiner Eigenschaften hinzufügt usw. Sie testen, ob sich nach dem Mutieren des Status Ihres Objekts ein Status-Accessor geändert hat (oder nicht). Das ist das allgemeine Muster Ihrer Tests: "Wenn ich mit meiner getesteten Klasse X mache, kann ich anschließend Y beobachten". Wie es zu Y kommt, ist kein Problem der Tests, und dies ist, was eingekapselt ist, und aus diesem Grund widerspricht TDD nicht der Einkapselung.quelle
Vermeide das Benutzen? Nein .
Vermeiden Sie mit Start ? Ja.
Ich stelle fest, dass Sie nicht gefragt haben, ob es in Ordnung ist, abstrakte Klassen mit TDD zu haben. Wenn Sie verstehen, wie abstrakte Klassen während der TDD entstehen, gilt das gleiche Prinzip auch für private Methoden.
Sie können Methoden in abstrakten Klassen nicht direkt testen, so wie Sie private Methoden nicht direkt testen können. Deshalb beginnen Sie nicht mit abstrakten Klassen und privaten Methoden. Sie beginnen mit konkreten Klassen und öffentlichen APIs und überarbeiten dann die allgemeinen Funktionen.
quelle