TDD Red-Green-Refactor und ob / wie man privat gewordene Methoden testet

91

Soweit ich das verstehe, scheinen sich die meisten Leute einig zu sein, dass private Methoden nicht direkt getestet werden sollten, sondern mit welchen öffentlichen Methoden auch immer. Ich kann ihren Punkt sehen, aber ich habe einige Probleme damit, wenn ich versuche, den "Drei Gesetzen von TDD" zu folgen und den "Rot-Grün-Refaktor" -Zyklus zu verwenden. Ich denke, das lässt sich am besten anhand eines Beispiels erklären:

Im Moment benötige ich ein Programm, das eine Datei (mit durch Tabulatoren getrennten Daten) lesen und alle Spalten herausfiltern kann, die nicht numerische Daten enthalten. Vermutlich gibt es bereits einige einfache Tools, um dies zu tun, aber ich habe mich dazu entschlossen, es von Grund auf neu zu implementieren, hauptsächlich, weil ich dachte, dass es ein schönes und sauberes Projekt für mich sein könnte, etwas Übung mit TDD zu bekommen.

Also setze ich zuerst den roten Hut auf, das heißt, ich brauche einen Test, der fehlschlägt. Ich dachte, ich brauche eine Methode, die alle nicht numerischen Felder in einer Zeile findet. Also schreibe ich einen einfachen Test, der natürlich nicht sofort kompiliert werden kann, also beginne ich, die Funktion selbst zu schreiben, und nach ein paar Zyklen hin und her (rot / grün) habe ich eine funktionierende Funktion und einen vollständigen Test.

Als nächstes fahre ich mit der Funktion "gatherNonNumericColumns" fort, die die Datei zeilenweise liest und in jeder Zeile meine Funktion "findNonNumericFields" aufruft, um alle Spalten zu sammeln, die schließlich entfernt werden müssen. Ein paar Rot-Grün-Zyklen, und ich habe wieder eine funktionierende Funktion und einen vollständigen Test.

Jetzt denke ich, ich sollte umgestalten. Da meine Methode "findNonNumericFields" nur deswegen entworfen wurde, weil ich dachte, sie würde für die Implementierung von "gatherNonNumericColumns" benötigt, erscheint es mir sinnvoll, "findNonNumericFields" privat werden zu lassen. Dies würde jedoch meine ersten Tests unterbrechen, da sie keinen Zugriff mehr auf die von ihnen getestete Methode hätten.

Also habe ich eine private Methode und eine Reihe von Tests, die es testen. Da so viele Leute raten, dass private Methoden nicht getestet werden sollten, habe ich das Gefühl, dass ich mich hier in eine Ecke gestrichen habe. Aber wo genau bin ich gescheitert?

Ich könnte auf einer höheren Ebene anfangen und einen Test schreiben, der testet, welche Methode irgendwann zu meiner öffentlichen Methode wird (findAndFilterOutAllNonNumericalColumns), der sich jedoch etwas gegen den ganzen Punkt von TDD anfühlt (zumindest laut Uncle Bob). : Dass Sie ständig zwischen dem Schreiben von Tests und Produktionscode wechseln sollten und dass alle Ihre Tests zu jedem Zeitpunkt in der letzten Minute oder so funktionierten. Denn wenn ich mit dem Schreiben eines Tests für eine öffentliche Methode beginne, wird es einige Minuten (oder Stunden oder sogar Tage in sehr komplexen Fällen) dauern, bis ich alle Details in den privaten Methoden zum Funktionieren gebracht habe, damit der Test die Öffentlichkeit testet Methode besteht.

Also, was ist zu tun? Ist TDD (mit dem schnellen Rot-Grün-Refaktor-Zyklus) einfach nicht mit privaten Methoden kompatibel? Oder liegt ein Fehler in meinem Design vor?

Henrik Berg
quelle
2
Entweder sind diese beiden Funktionen unterschiedlich genug, um unterschiedliche Einheiten zu sein - in diesem Fall sollten sich die privaten Methoden wahrscheinlich in ihren eigenen Klassen befinden -, oder sie sind die gleichen Einheiten. In diesem Fall verstehe ich nicht, warum Sie Tests für schreiben geräteinternes Verhalten . In Bezug auf den vorletzten Absatz sehe ich den Konflikt nicht. Warum müssten Sie eine ganze komplexe private Methode schreiben, um einen Testfall zu bestehen? Warum nicht schrittweise über die öffentliche Methode austreiben oder mit der Inline-Methode beginnen und dann extrahieren?
Ben Aaronson
26
Warum nehmen die Leute Redewendungen und Klischees aus Programmierbüchern und Blogs als tatsächliche Richtlinien für das Programmieren, ist mir ein Rätsel.
AK_
7
Genau aus diesem Grund mag ich TDD nicht: Wenn Sie sich in einem neuen Bereich befinden, werden Sie viel zusätzliche Arbeit leisten, um herauszufinden, wie die Architektur aussehen sollte und wie bestimmte Dinge funktionieren. Auf der anderen Seite: Wenn Sie sich in einem Gebiet befinden, mit dem Sie bereits vertraut sind, ist es von Vorteil, die Tests zunächst zu schreiben, ohne Sie zu ärgern, da Intellisense nicht versteht, warum Sie nicht kompilierbaren Code schreiben. Ich bin ein viel größerer Fan davon, über das Design nachzudenken, es zu schreiben und es dann zu testen.
Jeroen Vannevel
1
"Die meisten Leute scheinen zuzustimmen, dass private Methoden nicht direkt getestet werden sollten" - nein, testen Sie eine Methode direkt, wenn dies sinnvoll ist. Verstecke es, als privateob es Sinn macht, es zu tun.
osa

Antworten:

44

Einheiten

Ich glaube, ich kann genau feststellen, wo das Problem angefangen hat:

Ich dachte, ich brauche eine Methode, die alle nicht numerischen Felder in einer Zeile findet.

Darauf sollte sich sofort die Frage stellen, ob es sich um eine separate testbare Einheit gatherNonNumericColumnsoder einen Teil derselben handelt.

Wenn die Antwort " Ja, getrennt " lautet , ist Ihre Vorgehensweise einfach: Diese Methode muss für eine entsprechende Klasse öffentlich sein, damit sie als Einheit getestet werden kann. Ihre Mentalität ist so etwas wie "Ich muss eine Methode testen und ich muss auch eine andere Methode testen"

Nach allem, was Sie sagen, haben Sie herausgefunden, dass die Antwort " Nein, Teil desselben " ist. Zu diesem Zeitpunkt sollte Ihr Plan nicht mehr darin bestehen, vollständig zu schreiben und zu testen und findNonNumericFields dann zu schreiben gatherNonNumericColumns. Stattdessen sollte es einfach zu schreiben sein gatherNonNumericColumns. Vorerst findNonNumericFieldssollte dies nur ein Teil des Ziels sein, an das Sie denken, wenn Sie Ihren nächsten roten Testfall auswählen und das Refactoring durchführen. Diesmal lautet Ihre Einstellung "Ich muss eine Methode testen und dabei sollte ich bedenken, dass meine fertige Implementierung wahrscheinlich diese andere Methode enthalten wird".


Einen kurzen Zyklus halten

Das oben Genannte sollte nicht zu den Problemen führen, die Sie in Ihrem vorletzten Absatz beschreiben:

Denn wenn ich mit dem Schreiben eines Tests für eine öffentliche Methode beginne, wird es einige Minuten (oder Stunden oder sogar Tage in sehr komplexen Fällen) dauern, bis ich alle Details in den privaten Methoden zum Funktionieren gebracht habe, damit der Test die Öffentlichkeit testet Methode besteht.

Zu keinem Zeitpunkt müssen Sie für diese Technik einen roten Test schreiben, der nur dann grün wird, wenn Sie die gesamte findNonNumericFieldsNeuentwicklung durchführen. Viel wahrscheinlicher ist, findNonNumericFieldsdass ein Code in der öffentlichen Methode, die Sie testen, als Inline-Code gestartet wird, der im Verlauf mehrerer Zyklen aufgebaut und schließlich während eines Refactorings extrahiert wird.


Fahrplan

Um eine ungefähre Roadmap für dieses Beispiel anzugeben: Ich kenne nicht die genauen Testfälle, die Sie verwendet haben, aber Sie haben gatherNonNumericColumnsals öffentliche Methode geschrieben. Dann stimmen die Testfälle höchstwahrscheinlich mit denen überein, für die Sie geschrieben haben. Dabei findNonNumericFieldswird jeweils eine Tabelle mit nur einer Zeile verwendet. Wenn dieses einzeilige Szenario vollständig implementiert wurde und Sie einen Test schreiben wollten, um Sie zu zwingen, die Methode zu extrahieren, würden Sie einen zweizeiligen Fall schreiben, bei dem Sie Ihre Iteration hinzufügen müssten.

Ben Aaronson
quelle
2
Ich denke, das ist hier die Antwort. Als ich TDD in einer OOP-Umgebung einführte, fiel es mir oft schwer, meine eigenen Bottom-up-Instinkte zu überwinden. Ja, Funktionen sollten klein sein, aber das ist nach dem Refactoring. Vorher können sie riesige Monolithen sein. +1
João Mendes
2
@ JoãoMendes Nun, ich bin mir nicht sicher, ob Sie vor dem Refactoring zum Zustand eines riesigen Monolithen gelangen sollten, insbesondere bei sehr kurzen RGR-Zyklen. Aber ja, innerhalb einer testbaren Einheit kann das Arbeiten von unten nach oben zu den Problemen führen, die das OP beschreibt.
Ben Aaronson
1
OK, ich glaube ich verstehe, wo es jetzt schief gelaufen ist. Vielen Dank an alle von Ihnen (markiert dieses als die Antwort, aber die meisten anderen Antworten sind ebenso hilfreich)
Henrik Berg
66

Viele Leute denken, dass Unit-Tests methodenbasiert sind. es ist nicht. Es sollte sich um die kleinste Einheit handeln, die Sinn macht. Für die meisten Dinge bedeutet dies, dass die Klasse genau das ist, was Sie als Ganzes testen sollten. Keine individuellen Methoden drauf.

Jetzt werden Sie natürlich Methoden für die Klasse aufrufen, aber Sie sollten sich die Tests als auf das Black-Box-Objekt zutreffend vorstellen, das Sie haben, damit Sie sehen können, welche logischen Operationen Ihre Klasse bereitstellt. Dies sind die Dinge, die Sie testen müssen. Wenn Ihre Klasse so groß ist, dass die logische Operation zu komplex ist, liegt ein Entwurfsproblem vor, das zuerst behoben werden sollte.

Eine Klasse mit tausend Methoden mag testbar erscheinen, aber wenn Sie jede Methode nur einzeln testen, testen Sie die Klasse nicht wirklich. Einige Klassen müssen sich möglicherweise in einem bestimmten Status befinden, bevor eine Methode aufgerufen wird, z. B. eine Netzwerkklasse, für die vor dem Senden von Daten eine Verbindung eingerichtet werden muss. Die Sendedatenmethode kann nicht unabhängig von der gesamten Klasse betrachtet werden.

Sie sollten also sehen, dass private Methoden für das Testen irrelevant sind. Wenn Sie Ihre privaten Methoden nicht ausführen können, indem Sie die öffentliche Schnittstelle Ihrer Klasse aufrufen, sind diese privaten Methoden unbrauchbar und werden ohnehin nicht verwendet.

Ich denke, viele Leute versuchen, private Methoden in testbare Einheiten umzuwandeln, weil es einfach erscheint, Tests für sie durchzuführen, aber dies geht zu weit mit der Testgranularität. Martin Fowler sagt

Obwohl ich mit der Vorstellung beginne, dass die Einheit eine Klasse ist, nehme ich oft eine Reihe eng verwandter Klassen und behandle sie als eine Einheit

Das ist für ein objektorientiertes System sehr sinnvoll, da Objekte als Einheiten konzipiert sind. Wenn Sie einzelne Methoden testen möchten, sollten Sie möglicherweise ein prozedurales System wie C erstellen oder stattdessen eine Klasse, die ausschließlich aus statischen Funktionen besteht.

gbjbaanb
quelle
14
Für mich scheint diese Antwort den TDD-Ansatz in der Frage des OP völlig zu ignorieren. Es ist nur eine Wiederholung der „nicht testen private Methoden“ Mantra, aber es erklärt nicht , wie TDD - das ist basiert tatsächlich Methode - vielleicht mit einem nicht-Methode basiert Unit - Test - Ansatz arbeiten.
Doc Brown
6
@DocBrown Nein, es antwortet ihm vollständig, indem es sagt, dass Sie Ihre Einheiten nicht übergranulieren und sich das Leben schwer machen. TDD basiert nicht auf Methoden, sondern auf Einheiten , wenn eine Einheit sinnvoll ist. Wenn Sie eine C-Bibliothek haben, ist jede Einheit eine Funktion. Wenn Sie eine Klasse haben, ist die Einheit ein Objekt. Wie Fowler sagt, besteht eine Einheit manchmal aus mehreren eng miteinander verbundenen Klassen. Ich denke, viele Leute betrachten Unit-Tests als Methode für Methode, nur weil einige dumme Tools Stubs basierend auf Methoden generieren.
gbjbaanb
3
@gbjbaanb: Versuchen Sie, einen Test vorzuschlagen, der es dem OP ermöglicht, seine "nicht numerischen Felder in einer Zeile sammeln" zuerst mit reinem TDD zu implementieren, ohne dass die öffentliche Schnittstelle der Klasse, die er schreiben möchte, bereits geschrieben ist.
Doc Brown
8
Ich muss @DocBrown hier zustimmen. Das Problem des Fragestellers ist nicht, dass er mehr Testgranularität wünscht, als er erreichen kann, ohne private Methoden zu testen. Es ist so, dass er versucht hat, einem strengen TDD-Ansatz zu folgen, und das hat ihn - ohne dies zu planen - dazu gebracht, an eine Wand zu stoßen, an der er plötzlich feststellt, dass er eine Reihe von Tests für eine Privatmethode hat. Diese Antwort hilft dabei nicht. Es ist eine gute Antwort auf eine Frage, nur diese nicht.
Ben Aaronson
7
@Matthew: Sein Fehler ist, dass er die Funktion überhaupt geschrieben hat. Im Idealfall hätte er die öffentliche Methode als Spaghetti-Code schreiben und sie dann im Refactor-Zyklus in eine private Funktion umgestalten sollen - im Refactor-Zyklus nicht als privat markieren.
Slebetman
51

Die Tatsache, dass Ihre Datenerfassungsmethoden komplex genug sind, um Tests zu verdienen, und sich von Ihrem Hauptziel trennen, um eigene Methoden zu sein, anstatt Teil einiger Schleifen zu sein, weist auf die Lösung hin: Machen Sie diese Methoden nicht privat, sondern zu Mitgliedern einer anderen Klasse das bietet Sammeln / Filtern / Tabellieren Funktionalität.

Dann schreiben Sie an einer Stelle Tests für die dummen Datenmungsaspekte der Helferklasse (z. B. "Unterscheiden von Zahlen von Zeichen") und an einer anderen Stelle Tests für Ihr primäres Ziel (z. B. "Abrufen der Verkaufszahlen"), und Sie ziehen an Sie müssen die grundlegenden Filtertests in den Tests für Ihre normale Geschäftslogik nicht wiederholen.

Ganz allgemein, wenn Ihre Klasse, die eine Sache ausführt, umfangreichen Code enthält, um eine andere Sache zu tun, die für ihren primären Zweck erforderlich ist, aber von diesem getrennt ist, sollte dieser Code in einer anderen Klasse leben und über öffentliche Methoden aufgerufen werden. Es sollte nicht in privaten Ecken einer Klasse versteckt werden, die diesen Code nur aus Versehen enthält. Dies verbessert gleichzeitig die Testbarkeit und Verständlichkeit.

Kilian Foth
quelle
Ja, ich stimme dir zu. Aber ich habe ein Problem mit Ihrer ersten Aussage, sowohl mit dem "ausreichend komplexen" Teil als auch mit dem "ausreichend separaten" Teil. Was "komplex genug" betrifft: Ich versuche, einen schnellen Rot-Grün-Zyklus durchzuführen, was bedeutet, dass ich vor dem Wechsel zum Testen (oder umgekehrt) höchstens eine Minute oder so codieren kann. Das bedeutet, dass meine Tests in der Tat sehr feinkörnig sind. Ich dachte, das wäre einer der Vorteile von TDD, aber vielleicht habe ich es übertrieben, so dass es zum Nachteil wird.
Henrik Berg
Zu "ausreichend getrennt": Ich habe (wieder von Onkel Bob) gelernt, dass Funktionen klein sein sollten und dass sie kleiner sein sollten. Also im Grunde versuche ich 3-4 Zeilenfunktionen zu machen. So ist mehr oder weniger die gesamte Funktionalität in eigene Methoden unterteilt, egal wie klein und einfach sie auch sein mögen.
Henrik Berg
Wie auch immer, ich bin der Meinung, dass die datenverändernden Aspekte (z. B. findNonNumericFields) wirklich privat sein sollten. Und wenn ich es in eine andere Klasse aufteile, muss ich es sowieso öffentlich machen, also verstehe ich den Sinn darin nicht ganz.
Henrik Berg
6
@HenrikBerg überlegen, warum Sie Objekte überhaupt haben - sie sind keine bequemen Möglichkeiten, Funktionen zu gruppieren, sondern eigenständige Einheiten, die die Arbeit mit komplexen Systemen erleichtern. Daher sollten Sie darüber nachdenken, die Klasse als eine Sache zu testen.
gbjbaanb
@ gbjbaanb Ich würde argumentieren, dass sie beide ein und dasselbe sind.
RubberDuck
29

Persönlich bin ich der Meinung, dass Sie bei der Erstellung der Tests zu weit in die Implementierung eingedrungen sind. Sie gingen davon aus, dass Sie bestimmte Methoden benötigen würden. Aber brauchen Sie sie wirklich, um das zu tun, was die Klasse tun soll? Würde die Klasse scheitern, wenn jemand mitkam und sie intern überarbeitete? Wenn Sie wurden mit der Klasse (und das sollte die Einstellung des Testers meiner Meinung nach ), könnte man wirklich weniger egal , wenn es eine explizite Methode für Zahlen zu überprüfen ist.

Sie sollten die öffentliche Schnittstelle einer Klasse testen. Die private Implementierung ist aus einem bestimmten Grund privat. Es ist nicht Teil der öffentlichen Schnittstelle, da es nicht benötigt wird und sich ändern kann. Es ist ein Implementierungsdetail.

Wenn Sie Tests für die öffentliche Schnittstelle schreiben, wird das Problem, auf das Sie gestoßen sind, nie tatsächlich auftreten. Entweder können Sie Testfälle für die öffentliche Schnittstelle erstellen, die Ihre privaten Methoden abdecken (großartig), oder Sie können nicht. In diesem Fall ist es möglicherweise an der Zeit, sich eingehende Gedanken über die privaten Methoden zu machen und sie möglicherweise zu vernichten, wenn sie ohnehin nicht erreichbar sind.

nvoigt
quelle
1
"Implementierungsdetails" sind Dinge wie "Habe ich eine XOR- oder temporäre Variable verwendet, um Ints zwischen Variablen auszutauschen". Geschützte / private Methoden haben wie alles andere Verträge. Sie nehmen Inputs auf, arbeiten damit und produzieren unter bestimmten Bedingungen Outputs. Alles mit einem Vertrag sollte letztendlich getestet werden - nicht unbedingt für diejenigen, die Ihre Bibliothek konsumieren, sondern für diejenigen, die sie warten und nach Ihnen modifizieren. Nur weil es nicht "öffentlich" ist, heißt das nicht, dass es nicht Teil einer API ist.
Knetic
11

Sie führen TDD nicht basierend auf den erwarteten internen Aufgaben der Klasse durch.

Ihre Testfälle sollten darauf basieren, was die Klasse / Funktionalität / das Programm mit der Außenwelt zu tun hat. In Ihrem Beispiel wird der Benutzer Ihre Leserklasse jemals mit to aufrufenfind all the non-numerical fields in a line?

Wenn die Antwort "nein" ist, ist es ein schlechter Test, an erster Stelle zu schreiben. Sie möchten den Test zur Funktionalität auf Klassen- / Schnittstellenebene schreiben - nicht auf die Ebene "Was muss die Klassenmethode implementieren, damit dies funktioniert? ", Wie Ihr Test lautet.

Der TDD-Fluss ist:

  • rot (was macht die Klasse / Objekt / Funktion / etc mit der Außenwelt)
  • grün (schreibe den Minimalcode, damit diese externe Welt funktioniert)
  • Refactor (was ist der bessere Code, um diese Arbeit zu machen)

Es ist NICHT zu tun, "weil ich X in Zukunft als private Methode benötigen werde, lass es mich implementieren und teste es zuerst." Wenn Sie dies bemerken, machen Sie die "rote" Phase falsch. Dies scheint hier Ihr Problem zu sein.

Wenn Sie häufig Tests für Methoden schreiben, die zu privaten Methoden werden, führen Sie eine der folgenden Aktionen aus:

  • Sie verstehen die Anwendungsfälle Ihrer Benutzeroberfläche / öffentlichen Ebene nicht richtig genug, um einen Test für sie zu schreiben
  • Dramatische Änderung Ihres Designs und Umgestaltung mehrerer Tests (was eine gute Sache sein kann, abhängig davon, ob diese Funktionalität in neueren Tests getestet wurde)
Enderland
quelle
9

Sie stoßen beim Testen im Allgemeinen auf ein weit verbreitetes Missverständnis.

Die meisten Testanfänger denken zunächst so:

  • Schreiben Sie einen Test für die Funktion F
  • implementieren F
  • schreibe einen Test für Funktion G
  • implementiere G mit einem Aufruf an F
  • schreibe einen Test für eine Funktion H
  • Implementiere H mit einem Aufruf an G

und so weiter.

Das Problem hierbei ist, dass Sie tatsächlich keinen Einheitentest für die Funktion H haben. Der Test, der H testen soll, testet tatsächlich gleichzeitig H, G und F.

Um dies zu lösen, muss man sich darüber im Klaren sein, dass testbare Einheiten niemals voneinander abhängen dürfen, sondern von ihren Schnittstellen . In Ihrem Fall, in dem die Einheiten einfache Funktionen sind, sind die Schnittstellen nur ihre Rufsignatur. Sie müssen daher G so implementieren, dass es mit jeder Funktion mit der gleichen Signatur wie F verwendet werden kann.

Wie genau dies gemacht werden kann, hängt von Ihrer Programmiersprache ab. In vielen Sprachen können Sie Funktionen (oder Zeiger darauf) als Argumente an andere Funktionen übergeben. Auf diese Weise können Sie jede Funktion einzeln testen.

Initcrash
quelle
3
Ich wünschte, ich könnte noch öfter darüber abstimmen. Ich würde es einfach so zusammenfassen: Sie haben Ihre Lösung nicht richtig aufgebaut.
Justin Ohms
In einer Sprache wie C ist dies sinnvoll, aber für OO-Sprachen, in denen die Unit im Allgemeinen eine Klasse sein sollte (mit öffentlichen und privaten Methoden), sollten Sie die Klasse testen, nicht jede private Methode für sich. Isolieren Sie Ihre Klassen, ja. Isolieren der Methoden in jeder Klasse, nein.
Gbjbaanb
8

Die Tests, die Sie während der testgesteuerten Entwicklung schreiben, sollen sicherstellen, dass eine Klasse ihre öffentliche API korrekt implementiert, und gleichzeitig sicherstellen, dass diese öffentliche API einfach zu testen und zu verwenden ist.

Sie können diese API auf jeden Fall mit privaten Methoden implementieren. Es ist jedoch nicht erforderlich, Tests über TDD zu erstellen. Die Funktionalität wird getestet, da die öffentliche API ordnungsgemäß funktioniert.

Nehmen wir nun an, Ihre privaten Methoden sind so kompliziert, dass sie eigenständige Tests verdienen - aber als Teil der öffentlichen API Ihrer ursprünglichen Klasse ergeben sie keinen Sinn. Nun, dies bedeutet wahrscheinlich, dass es sich tatsächlich um öffentliche Methoden für eine andere Klasse handeln sollte - eine, die Ihre ursprüngliche Klasse in ihrer eigenen Implementierung ausnutzt.

Indem Sie nur die öffentliche API testen, können Sie die Implementierungsdetails künftig viel einfacher ändern. Nicht hilfreiche Tests ärgern Sie später nur, wenn sie neu geschrieben werden müssen, um einige elegante Umgestaltungen zu unterstützen, die Sie gerade entdeckt haben.

Bill Michell
quelle
4

Ich denke, die richtige Antwort ist die Schlussfolgerung, zu der Sie gekommen sind, als Sie mit den öffentlichen Methoden angefangen haben. Sie würden damit beginnen, einen Test zu schreiben, der diese Methode aufruft. Es würde fehlschlagen, also erstellen Sie eine Methode mit diesem Namen, die nichts tut. Dann korrigieren Sie vielleicht einen Test, der nach einem Rückgabewert sucht.

(Ich weiß nicht genau, was Ihre Funktion tut. Gibt sie eine Zeichenfolge mit dem Dateiinhalt zurück, bei der die nicht numerischen Werte entfernt wurden?)

Wenn Ihre Methode eine Zeichenfolge zurückgibt, suchen Sie nach diesem Rückgabewert. Sie bauen es also einfach weiter auf.

Ich denke, alles, was in einer privaten Methode passiert, sollte zu einem bestimmten Zeitpunkt während Ihres Prozesses in der öffentlichen Methode sein und dann nur als Teil eines Umgestaltungsschritts in die private Methode verschoben werden. Refactoring erfordert meines Wissens keine fehlgeschlagenen Tests. Sie müssen nur fehlerhafte Tests durchführen, wenn Sie Funktionen hinzufügen. Sie müssen Ihre Tests nur nach dem Refactor ausführen, um sicherzustellen, dass alle bestanden werden.

Matt Dyer
quelle
3

Es fühlt sich an, als hätte ich mich hier in eine Ecke gemalt. Aber wo genau bin ich gescheitert?

Es gibt ein altes Sprichwort.

Wenn Sie nicht planen, planen Sie auch nicht.

Die Leute scheinen zu glauben, dass man sich beim TDD einfach hinsetzt, Tests schreibt und das Design einfach magisch abläuft. Das stimmt nicht. Sie benötigen einen übergeordneten Plan. Ich habe festgestellt, dass TDD meine besten Ergebnisse liefert, wenn ich zuerst die Schnittstelle (öffentliche API) entwerfe. Persönlich erstelle ich ein Ist interface, das die Klasse zuerst definiert.

keuchen Ich schrieb etwas "Code", bevor ich irgendwelche Tests schrieb! Nun, nein. Habe ich nicht. Ich habe einen Vertrag geschrieben , der befolgt werden muss, ein Design . Ich vermute, Sie könnten ähnliche Ergebnisse erzielen, wenn Sie ein UML-Diagramm auf Millimeterpapier notieren. Der Punkt ist, Sie müssen einen Plan haben. TDD ist keine Lizenz, um ein Stück Code zu hacken.

Ich habe wirklich das Gefühl, dass "Test First" eine falsche Bezeichnung ist. Erst entwerfen, dann testen.

Befolgen Sie natürlich die Ratschläge, die andere zum Extrahieren weiterer Klassen aus Ihrem Code gegeben haben. Wenn Sie das Bedürfnis haben, die Interna einer Klasse zu testen, extrahieren Sie diese Interna in eine einfach zu testende Einheit und injizieren Sie sie.

Badeente
quelle
2

Denken Sie daran, dass Tests auch überarbeitet werden können! Wenn Sie eine Methode privat machen, reduzieren Sie die öffentliche API, und daher ist es durchaus akzeptabel, einige entsprechende Tests für diese "verlorene Funktionalität" (AKA-reduzierte Komplexität) wegzuwerfen.

Andere haben angegeben, dass Ihre private Methode entweder als Teil Ihrer anderen API-Tests aufgerufen wird oder nicht erreichbar und daher löschbar ist. In der Tat sind die Dinge feinkörniger, wenn wir über Ausführungspfade nachdenken .

Wenn wir zum Beispiel eine öffentliche Methode haben, die eine Division durchführt, möchten wir vielleicht den Pfad testen, der eine Division durch Null ergibt. Wenn wir die Methode privat machen, haben wir die Wahl: Entweder können wir den Pfad der Division durch Null berücksichtigen, oder wir können diesen Pfad eliminieren, indem wir überlegen, wie er von den anderen Methoden aufgerufen wird.

Auf diese Weise können wir einige Tests wegwerfen (z. B. durch Null teilen) und die anderen in Bezug auf die verbleibende öffentliche API umgestalten. In einer idealen Welt kümmern sich die vorhandenen Tests natürlich um alle verbleibenden Pfade, aber die Realität ist immer ein Kompromiss;)

Warbo
quelle
1
Während die anderen Antworten insofern richtig sind, als die private Methode nicht in den roten Kreislauf hätte geschrieben werden dürfen, machen die Menschen Fehler. Und wenn Sie den Weg des Fehlers weit genug zurückgelegt haben, ist dies die geeignete Lösung.
Slebetman
2

Es gibt Zeiten, in denen eine private Methode zu einer öffentlichen Methode einer anderen Klasse gemacht werden kann.

Beispielsweise verfügen Sie möglicherweise über private Methoden, die nicht threadsicher sind und die Klasse in einem temporären Zustand belassen. Diese Methoden können in eine separate Klasse verschoben werden, die von Ihrer ersten Klasse privat verwaltet wird. Wenn es sich bei Ihrer Klasse also um eine Warteschlange handelt, können Sie eine InternalQueue-Klasse mit öffentlichen Methoden haben, und die Queue-Klasse enthält die InternalQueue-Instanz privat. Auf diese Weise können Sie die interne Warteschlange testen und die einzelnen Vorgänge in der InternalQueue ermitteln.

(Dies ist am offensichtlichsten, wenn Sie sich vorstellen, dass es keine List-Klasse gibt, und wenn Sie versucht haben, die List-Funktionen als private Methoden in der Klasse zu implementieren, die sie verwendet.)

Thomas Andrews
quelle
2
"Es gibt Zeiten, in denen eine private Methode zu einer öffentlichen Methode einer anderen Klasse gemacht werden kann." Das kann ich gar nicht genug betonen. Manchmal ist eine private Methode einfach eine andere Klasse, die nach ihrer eigenen Identität schreit.
0

Ich frage mich, warum Ihre Sprache nur zwei Ebenen der Privatsphäre hat, vollständig öffentlich und vollständig privat.

Können Sie dafür sorgen, dass Ihre nicht öffentlichen Methoden paketzugänglich sind oder so? Legen Sie dann Ihre Tests in dasselbe Paket und genießen Sie es, das Innenleben zu testen, das nicht Teil der öffentlichen Schnittstelle ist. Ihr Build-System schließt Tests beim Erstellen einer Release-Binärdatei aus.

Natürlich müssen Sie manchmal wirklich private Methoden haben, auf die nur die definierende Klasse zugreifen kann. Ich hoffe, alle diese Methoden sind sehr klein. Im Allgemeinen hilft es viel, die Methoden klein zu halten (z. B. unter 20 Zeilen): Testen, Warten und einfaches Verstehen des Codes werden einfacher.

9000
quelle
3
Das Ändern eines Methodenzugriffsmodifikators, um nur Tests auszuführen, ist eine Situation, in der ein Schwanz einen Hund wackelt. Ich denke, dass das Testen der Innenteile einer Einheit die spätere Umgestaltung nur erschwert. Im Gegensatz dazu ist das Testen der öffentlichen Schnittstelle großartig, da es als "Vertrag" für eine Einheit fungiert.
Scriptin
Sie müssen die Zugriffsebene einer Methode nicht ändern. Was ich damit sagen will, ist, dass Sie über eine Zwischenzugriffsebene verfügen, mit der Sie bestimmten Code, einschließlich Tests, einfacher schreiben können, ohne einen öffentlichen Vertrag abzuschließen. Natürlich müssen Sie die öffentliche Schnittstelle testen, aber manchmal ist es auch von Vorteil, einige der inneren Funktionen isoliert zu testen.
9000,
0

Ich habe gelegentlich private Methoden zum Schutz übergangen, um feinkörnigere Tests zu ermöglichen (enger als die offen gelegte öffentliche API). Dies sollte eher die (hoffentlich sehr seltene) Ausnahme als die Regel sein, kann jedoch in bestimmten Fällen hilfreich sein, auf die Sie möglicherweise stoßen. Dies ist auch etwas, worauf Sie beim Erstellen einer öffentlichen API überhaupt nicht achten sollten. Es handelt sich eher um "Betrug", den man in diesen seltenen Situationen für Software zur internen Verwendung verwenden kann.

Brian Knoblauch
quelle
0

Ich habe das erlebt und deinen Schmerz gespürt.

Meine Lösung war:

hören Sie auf, Tests wie den Bau eines Monolithen zu behandeln.

Denken Sie daran, dass Sie, wenn Sie eine Reihe von Tests geschrieben haben, z. B. 5, um einige Funktionen auszuschalten, nicht alle diese Tests beibehalten müssen , insbesondere wenn dies Teil eines anderen Objekts ist.

Zum Beispiel habe ich oft:

  • Niedrigstandstest 1
  • Code, um es zu erfüllen
  • Test auf niedrigem Niveau 2
  • Code, um es zu erfüllen
  • Test auf niedrigem Niveau 3
  • Code, um es zu erfüllen
  • Niedrigstandstest 4
  • Code, um es zu erfüllen
  • Test auf niedrigem Niveau 5
  • Code, um es zu erfüllen

also dann habe ich

  • Niedrigstandstest 1
  • Test auf niedrigem Niveau 2
  • Test auf niedrigem Niveau 3
  • Niedrigstandstest 4
  • Test auf niedrigem Niveau 5

Wenn ich jedoch jetzt Funktionen höherer Ebenen hinzufüge, die diese Funktion aufrufen und über eine Vielzahl von Tests verfügen, kann ich diese Tests niedrigerer Ebenen möglicherweise auf Folgendes reduzieren:

  • Niedrigstandstest 1
  • Test auf niedrigem Niveau 5

Der Teufel steckt im Detail und die Fähigkeit, dies zu tun, wird von den Umständen abhängen.

Michael Durrant
quelle
-2

Dreht sich die Sonne um die Erde oder die Erde um die Sonne? Laut Einstein lautet die Antwort ja oder beides, da sich beide Modelle nur durch den Gesichtspunkt unterscheiden. Ebenso stehen die Verkapselung und die testgetriebene Entwicklung nur in Konflikt, weil wir glauben, dass dies der Fall ist. Wir sitzen hier wie Galileo und der Papst und schleudern Beleidigungen aufeinander: Dummkopf, sehen Sie nicht, dass auch private Methoden getestet werden müssen? Ketzer, brecht die Einkapselung nicht! Ebenso können wir, wenn wir erkennen, dass die Wahrheit größer ist als wir dachten, versuchen, die Tests für die privaten Schnittstellen so zu kapseln, dass die Tests für die öffentlichen Schnittstellen die Kapselung nicht aufbrechen.

Versuchen Sie Folgendes: Fügen Sie zwei Methoden hinzu, eine, die keine Eingabe hat, aber nur die Anzahl der privaten Tests zurückgibt, und eine, die eine Testnummer als Parameter verwendet und bestanden / nicht bestanden zurückgibt.

hildred
quelle
1
Die gewählten Beleidigungen wurden von Galilei und dem Papst verwendet, ohne dass eine Antwort auf diese Frage gefunden worden wäre.
Hildred