So vermeiden Sie den Unit-Test privater Methoden

15

Ich weiß, dass Sie keine privaten Methoden testen sollen, und wenn es so aussieht, als müsste es eine Klasse geben, die darauf wartet, herauszukommen.

Aber ich möchte keine Unmengen von Klassen haben, nur damit ich ihre öffentlichen Schnittstellen testen kann, und ich finde, dass ich für viele Klassen, wenn ich nur die öffentlichen Methoden teste, am Ende viele Abhängigkeiten verspotten muss und die Unit-Tests es sind enorm und schwer zu folgen.

Ich bevorzuge es sehr, die privaten Methoden zu verspotten, wenn ich die öffentlichen teste, und externe Abhängigkeiten zu verspotten, wenn ich die privaten teste.

Bin ich verrückt?

Fran Sevillano
quelle
Ein gründlicher Komponententest sollte implizit alle privaten Mitglieder einer bestimmten Klasse abdecken, da Sie sie zwar nicht direkt aufrufen können, ihr Verhalten sich jedoch weiterhin auf die Ausgabe auswirkt. Wenn nicht, warum sind sie dann überhaupt da? Denken Sie beim Unit-Test daran, dass Ihnen das Ergebnis am Herzen liegt und nicht, wie das Ergebnis erzielt wurde.
GordonM

Antworten:

23

Sie haben teilweise recht - Sie sollten private Methoden nicht direkt testen. Die privaten Methoden einer Klasse sollten von einer oder mehreren der öffentlichen Methoden aufgerufen werden (möglicherweise indirekt - eine private Methode, die von einer öffentlichen Methode aufgerufen wird, kann andere private Methoden aufrufen). Wenn Sie Ihre öffentlichen Methoden testen, testen Sie daher auch Ihre privaten Methoden. Wenn Sie über nicht getestete private Methoden verfügen, sind entweder Ihre Testfälle unzureichend oder die privaten Methoden werden nicht verwendet und können entfernt werden.

Wenn Sie einen White-Box-Testansatz verwenden, sollten Sie die Implementierungsdetails Ihrer privaten Methoden berücksichtigen, wenn Sie Komponententests für Ihre öffentlichen Methoden erstellen. Wenn Sie einen Black-Box-Ansatz verwenden, sollten Sie keine Implementierungsdetails in den öffentlichen oder privaten Methoden testen, sondern das erwartete Verhalten.

Persönlich bevorzuge ich einen White-Box-Ansatz gegenüber Unit-Tests. Ich kann Tests erstellen, um die zu testenden Methoden und Klassen in verschiedene Zustände zu versetzen, die bei meinen öffentlichen und privaten Methoden ein interessantes Verhalten hervorrufen, und dann behaupten, dass die Ergebnisse den Erwartungen entsprechen.

Verspotten Sie also nicht Ihre privaten Methoden. Verwenden Sie sie, um zu verstehen, was Sie testen müssen, um eine gute Abdeckung der von Ihnen bereitgestellten Funktionen zu erhalten. Dies gilt insbesondere für das Unit-Test-Level.

Thomas Owens
quelle
1
"Verspotten Sie nicht Ihre privaten Methoden" Ja, ich kann das Testen verstehen, wenn Sie sie verspotten, haben Sie möglicherweise die feine Linie zu verrückt überschritten
Ewan
Ja, aber wie gesagt, mein Problem mit White-Box-Tests ist, dass das Verspotten aller Abhängigkeiten für öffentliche und private Methoden normalerweise zu sehr großen Testmethoden führt. Irgendwelche Ideen, wie man das angeht?
Fran Sevillano
8
@FranSevillano Wenn Sie so viel stutzen oder verspotten müssen, würde ich mich um Ihr Gesamtdesign kümmern. Etwas fühlt sich ab.
Thomas Owens
Ein Code-Coverage-Tool hilft dabei.
Andy
5
@FranSevillano: Es gibt nicht viele gute Gründe für eine Klasse, die eine Sache tut, um Tonnen von Abhängigkeiten zu haben. Wenn Sie Tonnen von Abhängigkeiten haben, haben Sie wahrscheinlich eine Gottklasse.
Mooing Duck
4

Ich halte das für eine sehr schlechte Idee.

Das Problem bei der Erstellung von Komponententests für private Mitglieder besteht darin, dass diese schlecht zum Produktlebenszyklus passen.

Der Grund, warum Sie sich dafür entschieden haben, diese Methoden privat zu machen, liegt darin, dass sie für das, was Ihre Klassen versuchen, nicht von zentraler Bedeutung sind - nur als Hilfestellung bei der Implementierung dieser Funktionalität. Wenn Sie umgestalten, sind diese privaten Details wahrscheinlich Kandidaten für eine Änderung und verursachen dann beim Umgestalten Reibungspunkte.

Ein wesentlicher Unterschied zwischen öffentlichen und privaten Mitgliedern besteht darin, dass Sie Ihre öffentliche API sorgfältig überlegen, gut dokumentieren und prüfen sollten (Behauptungen usw.). Aber mit einer privaten API wäre es sinnlos, genau darüber nachzudenken (ein vergeudeter Aufwand, da die Verwendung so lokalisiert ist). Das Einfügen privater Methoden in Komponententests bedeutet, dass externe Abhängigkeiten von diesen Methoden erstellt werden. Dies bedeutet, dass die API stabil und gut dokumentiert sein muss (da jemand herausfinden muss, warum diese Komponententests fehlgeschlagen sind, wenn dies der Fall ist).

Ich schlage dich vor:

  • Überlegen Sie, ob diese Methoden privat sein sollen
  • Schreiben Sie einmalige Tests (nur um sicherzugehen, dass sie jetzt korrekt sind, machen Sie sie vorübergehend öffentlich, damit Sie sie testen können, und löschen Sie sie dann).
  • Integrierte bedingte Tests in Ihre Implementierung mit #ifdef und Assertions (Tests werden nur in Debugbuilds durchgeführt).

Schätzen Sie Ihre Neigung, diese Funktionalität zu testen, und dass es schwierig ist, sie über Ihre aktuelle öffentliche API zu testen. Aber ich persönlich schätze Modularität mehr als Testabdeckung.

Lewis Pringle
quelle
6
Ich stimme dir nicht zu. IMO, dies ist 100% korrekt für Integrationstests. Bei Unit-Tests ist das jedoch anders. Das Ziel von Unit-Tests ist es, genau zu bestimmen, wo ein Fehler vorliegt, der eng genug ist, damit Sie ihn schnell beheben können. Ich befinde mich oft in dieser Situation: Ich habe nur sehr wenige öffentliche Methoden, weil das wirklich der Kernwert meiner Klassen ist (wie Sie sagten, sollte man das tun). Allerdings vermeide ich es auch, 400-Zeilen-Methoden zu schreiben, weder öffentlich noch privat. Meine wenigen öffentlichen Methoden können ihr Ziel also nur mit Hilfe von zehn privaten Methoden erreichen. Das ist zu viel Code, um es "schnell zu beheben". Ich muss den Debugger starten usw.
Marstato
6
@marstato: Vorschlag: Schreibe zuerst Tests und ändere deine Meinung zu Unittests: Sie finden keine Fehler, stellen aber sicher, dass der Code wie vom Entwickler beabsichtigt funktioniert.
Timothy Truckle
@ Marstato Vielen Dank! Ja. Die Tests bestehen immer beim ersten Einchecken (oder Sie hätten es nicht eingecheckt!). Sie sind nützlich, wenn Sie den Code weiterentwickeln, wenn Sie etwas kaputtmachen und wenn Sie gute Regressionstests haben, geben sie Ihnen KOMFORT / LEITFADEN, wo Sie nach Problemen suchen können (nicht in den Sachen, die stabil sind und gut auf Regression getestet).
Lewis Pringle
@marstato "Das Ziel von Unit-Tests ist es, herauszufinden, wo sich ein Fehler befindet" - genau das ist das Missverständnis, das zur Frage des OP führt. Das Ziel von Unit-Tests ist es, das beabsichtigte (und vorzugsweise dokumentierte) Verhalten einer API zu überprüfen.
user560822
4
@marstato Der Name "Integrationstest" kommt von dem Test, dass mehrere Komponenten zusammenarbeiten (dh sich ordnungsgemäß integrieren). Beim Komponententest wird eine einzelne Komponente isoliert getestet, was bedeutet, dass ihre öffentliche API wie dokumentiert / erforderlich funktioniert. Keines dieser beiden Begriffe sagt nichts darüber , ob Sie gehören Tests der internen Implementierungslogik als Teil der sichergestellt wird, dass die Integration funktioniert, oder dass die Einheit funktioniert.
Ben
3

UnitTests testen das öffentlich beobachtbare Verhalten , nicht den Code, wobei "öffentlich" bedeutet: Rückgabewerte und Kommunikation mit Abhängigkeiten.

Eine "Einheit" ist jeder Code, der das gleiche Problem löst (oder genauer gesagt: hat den gleichen Grund, sich zu ändern). Das kann eine einzelne Methode oder eine Reihe von Klassen sein.

Der Hauptgrund, warum Sie nicht testen möchten, private methodsist, dass es sich um Implementierungsdetails handelt und Sie diese möglicherweise während des Refactorings ändern möchten (verbessern Sie Ihren Code, indem Sie OO-Prinzipien anwenden, ohne die Funktionalität zu ändern). Dies ist genau der Fall, wenn Sie nicht möchten, dass sich Ihre Unittests ändern, um sicherzustellen , dass sich das Verhalten Ihres CuT während des Refactorings nicht geändert hat.

Aber ich möchte keine Unmengen von Klassen haben, nur damit ich ihre öffentlichen Schnittstellen testen kann, und ich finde, dass ich für viele Klassen, wenn ich nur die öffentlichen Methoden teste, am Ende viele Abhängigkeiten verspotten muss und die Unit-Tests es sind enorm und schwer zu folgen.

Normalerweise erlebe ich das Gegenteil: Je kleiner die Klassen sind (je weniger Verantwortung sie haben), desto weniger Abhängigkeiten haben sie und desto einfacher ist es, sowohl zu schreiben als auch zu lesen.

Idealerweise wenden Sie dasselbe Abstraktionsmuster auf Ihre Klassen an. Dies bedeutet, dass Ihre Klassen entweder Geschäftslogik (vorzugsweise als "reine Funktionen", die nur an ihren Parametern arbeiten, ohne einen eigenen Zustand beizubehalten) (x) oder Methoden für andere Objekte aufrufen, nicht beide gleichzeitig.

Auf diese Weise wird das Unittesting des Geschäftsverhaltens zum Kinderspiel, und die "Delegierungs" -Objekte sind in der Regel zu einfach, um fehlzuschlagen (keine Verzweigung, keine Statusänderung), sodass kein Unittesting erforderlich ist und ihre Prüfung den Integrations- oder Modultests überlassen werden kann

Timothy Truckle
quelle
1

Unit-Tests haben einen gewissen Wert, wenn Sie der einzige Programmierer sind, der an dem Code arbeitet, insbesondere wenn die Anwendung sehr umfangreich oder sehr komplex ist. Wenn eine größere Anzahl von Programmierern an derselben Codebasis arbeitet, sind Komponententests unabdingbar. Das Konzept des Komponententests wurde eingeführt, um einige der Schwierigkeiten bei der Arbeit in diesen größeren Teams anzugehen.

Der Grund, warum Unit-Tests größeren Teams helfen, sind Verträge. Wenn mein Code von jemand anderem geschriebenen Code anruft, gehe ich davon aus, was der Code der anderen Person in verschiedenen Situationen tun wird. Vorausgesetzt, diese Annahmen sind immer noch wahr, funktioniert mein Code immer noch. Aber woher weiß ich, welche Annahmen gültig sind und woher ich weiß, wann sich diese Annahmen geändert haben?

Hier kommen Komponententests ins Spiel. Der Autor einer Klasse erstellt Komponententests, um das erwartete Verhalten ihrer Klasse zu dokumentieren. Der Komponententest definiert alle gültigen Verwendungsarten der Klasse, und die Ausführung des Komponententests überprüft, ob diese Anwendungsfälle wie erwartet funktionieren. Ein anderer Programmierer, der von Ihrer Klasse Gebrauch machen möchte, kann Ihre Komponententests lesen, um das Verhalten zu verstehen, das er für Ihre Klasse erwarten kann, und dies als Grundlage für seine Annahmen über die Funktionsweise Ihrer Klasse verwenden.

Auf diese Weise bilden die öffentlichen Methodensignaturen der Klasse und die Komponententests zusammen einen Vertrag zwischen dem Klassenautor und den anderen Programmierern, die diese Klasse in ihrem Code verwenden.

Was passiert in diesem Szenario, wenn Sie private Methoden testen? Klar macht es keinen Sinn.

Wenn Sie der einzige Programmierer sind, der an Ihrem Code arbeitet und Unit-Tests zum Debuggen Ihres Codes verwenden möchten, sehe ich darin keinen Schaden, es ist nur ein Tool, und Sie können es so verwenden, wie es funktioniert Sie, aber es war nicht der Grund, warum Unit-Tests eingeführt wurden, und bietet nicht die Hauptvorteile von Unit-Tests.

bikeman868
quelle
Ich kämpfe damit, indem ich die Abhängigkeiten so herausnehme, dass mein Test nicht fehlschlägt, wenn eine Abhängigkeit ihr Verhalten ändert. Soll dieser Fehler bei einem Integrationstest auftreten, nicht bei einem Komponententest?
Todd
Der Mock soll sich identisch zur tatsächlichen Implementierung verhalten, aber keine Abhängigkeiten aufweisen. Wenn sich die tatsächliche Implementierung ändert, sodass sie sich jetzt unterscheidet, würde dies als ein Fehlschlag des Integrationstests angezeigt. Persönlich betrachte ich die Entwicklung von Mocks und die Umsetzung als eine Aufgabe. Ich baue keine Mocks im Rahmen von Unit-Tests. Auf diese Weise werden beim Ausführen der Komponententests alle anderen Klassen identifiziert, die durch diese Änderung beschädigt wurden, wenn ich das Verhalten meiner Klassen ändere und den Schein anpasse.
Bikeman868
1

Bevor Sie eine solche Frage beantworten, müssen Sie entscheiden, was Sie tatsächlich erreichen möchten.

Sie schreiben Code. Sie hoffen, dass es seinen Vertrag erfüllt (mit anderen Worten, es tut, was es tun soll. Das Aufschreiben dessen, was es tun soll, ist für einige Menschen ein riesiger Fortschritt).

Um einigermaßen davon überzeugt zu sein, dass der Code das tut, was er tun soll, starren Sie entweder lange genug darauf oder Sie schreiben Testcode, der genügend Fälle testet, um Sie davon zu überzeugen, dass "wenn der Code alle diese Tests besteht, ist er korrekt".

Oft interessiert Sie nur die öffentlich definierte Oberfläche eines Codes. Wenn ich Ihre Bibliothek verwenden, es ist mir egal , wie Sie es vielleicht richtig funktionieren, nur , dass es funktioniert richtig funktioniert. Ich überprüfe, ob Ihre Bibliothek korrekt ist, indem ich Komponententests durchführe.

Aber Sie erstellen die Bibliothek. Es kann schwierig sein, es richtig zum Laufen zu bringen. Angenommen, ich kümmere mich nur darum, dass die Bibliothek die Operation X korrekt ausführt, sodass ich einen Komponententest für X durchführe. Sie, der Entwickler, der für die Erstellung der Bibliothek verantwortlich ist, implementieren X, indem Sie die Schritte A, B und C kombinieren, die jeweils völlig untrivial sind. Um Ihre Bibliothek zum Laufen zu bringen, fügen Sie Tests hinzu, um zu überprüfen, ob A, B und C jeweils korrekt funktionieren. Sie wollen diese Tests. Zu sagen "Du solltest keine Unit-Tests für private Methoden haben" ist ziemlich sinnlos. Sie möchten Tests für diese privaten Methoden. Vielleicht sagt dir jemand, dass das Testen privater Methoden falsch ist. Das bedeutet aber nur, dass Sie sie möglicherweise nicht "Komponententests", sondern "private Tests" nennen oder wie auch immer Sie sie nennen möchten.

Die Sprache Swift löst das Problem, dass Sie A, B, C nicht als öffentliche Methoden verfügbar machen möchten, nur weil Sie sie testen möchten, indem Sie Funktionen das Attribut "testable" zuweisen. Der Compiler ermöglicht den Aufruf von privaten testbaren Methoden aus Unit-Tests, jedoch nicht aus Nicht-Test-Code.

gnasher729
quelle
0

Ja, du bist verrückt ... wie ein Fuchs!

Es gibt verschiedene Möglichkeiten, private Methoden zu testen, von denen einige sprachabhängig sind.

  • Betrachtung! Regeln sind gemacht um gebrochen zu werden!
  • machen sie geschützt, erben und überschreiben
  • Freund / InternalsVisibleTo Klassen

Wenn Sie jedoch private Methoden testen möchten, möchten Sie sie wahrscheinlich in Abhängigkeit von einer öffentlichen Methode verschieben und diese testen / injizieren.

Ewan
quelle
Ich weiß nicht, in meinen Augen hast du erklärt, dass es keine gute Idee ist, aber weiter auf die Frage
Liath
Ich dachte, ich hätte alle Grundlagen abgedeckt :(
Ewan
3
Ich habe zugestimmt, weil die Idee, Ihre privaten Hilfsmethoden der öffentlichen API zur Verfügung zu stellen, nur um sie zu testen, verrückt ist, und ich habe das immer gedacht.
Robert Harvey
0

Bleib pragmatisch. Das Testen von Sonderfällen in privaten Methoden, indem der Zustand der Instanz und die Parameter für eine öffentliche Methode so eingerichtet werden, dass diese Fälle dort auftreten, ist häufig viel zu kompliziert.

Ich füge einen zusätzlichen internalAccessor hinzu (zusammen mit dem Flag InternalsVisibleToder Testassembly), der eindeutig benannt ist DoSomethingForTesting(parameters), um diese "privaten" Methoden zu testen.

Natürlich kann sich die Implementierung irgendwann ändern, und diese Tests, einschließlich der Test-Accessoren, sind veraltet. Das ist immer noch besser als ungetestete Fälle oder nicht lesbare Tests.

Bernhard Hiller
quelle