410 Stimmen

Sollte ich private Methoden oder nur öffentliche Methoden testen?

Ich habe gelesen diese Stelle darüber, wie man private Methoden testet. Ich teste sie normalerweise nicht, weil ich immer dachte, dass es schneller ist, nur öffentliche Methoden zu testen, die von außerhalb des Objekts aufgerufen werden. Testen Sie private Methoden? Sollte ich sie immer testen?

392voto

jop Punkte 80065

Ich teste keine privaten Methoden. Eine private Methode ist ein Implementierungsdetail, das für die Benutzer der Klasse verborgen sein sollte. Das Testen privater Methoden bricht die Kapselung.

Wenn ich feststelle, dass die private Methode groß oder komplex oder wichtig genug ist, um eigene Tests zu erfordern, füge ich sie einfach in eine andere Klasse ein und mache sie dort öffentlich ( Methode Objekt ). Dann kann ich die zuvor private, jetzt aber öffentliche Methode, die jetzt in ihrer eigenen Klasse lebt, leicht testen.

340voto

Dave Sherohman Punkte 44017

Was ist der Zweck von Tests?

Die meisten der bisherigen Antworten besagen, dass private Methoden Implementierungsdetails sind, die keine Rolle spielen (oder zumindest nicht spielen sollten), solange die öffentliche Schnittstelle gut getestet ist und funktioniert. Das ist absolut richtig wenn Ihr einziger Zweck beim Testen darin besteht, sicherzustellen, dass die öffentliche Schnittstelle funktioniert .

Ich persönlich nutze Code-Tests in erster Linie, um sicherzustellen, dass künftige Code-Änderungen keine Probleme verursachen, und um meine Debugging-Bemühungen zu unterstützen, falls sie doch auftreten. Ich finde, dass das Testen der privaten Methoden genauso gründlich wie das der öffentlichen Schnittstelle (wenn nicht sogar noch gründlicher!) diesen Zweck fördert.

Bedenken Sie: Sie haben eine öffentliche Methode A, die die private Methode B aufruft. A und B verwenden beide die Methode C. C wird geändert (vielleicht von Ihnen, vielleicht von einem Anbieter), wodurch A anfängt, seine Tests nicht mehr zu bestehen. Wäre es nicht nützlich, auch Tests für B zu haben, obwohl es privat ist, damit Sie wissen, ob das Problem in der Verwendung von C durch A, in der Verwendung von C durch B oder in beiden liegt?

Das Testen privater Methoden ist auch dann von Nutzen, wenn die Testabdeckung der öffentlichen Schnittstelle unvollständig ist. Dies ist zwar eine Situation, die wir im Allgemeinen vermeiden wollen, aber die Effizienz von Unit-Tests hängt sowohl davon ab, dass die Tests Fehler finden, als auch von den damit verbundenen Entwicklungs- und Wartungskosten. In einigen Fällen kann der Nutzen einer 100%igen Testabdeckung als nicht ausreichend erachtet werden, um die Kosten für diese Tests zu rechtfertigen, was zu Lücken in der Testabdeckung der öffentlichen Schnittstelle führt. In solchen Fällen kann ein gezielter Test einer privaten Methode eine sehr effektive Ergänzung der Codebasis sein.

175voto

Rosellyne Worrall Punkte 2014

Ich neige dazu, den Rat von Dave Thomas und Andy Hunt in ihrem Buch zu befolgen Pragmatische Einheitstests :

Im Allgemeinen wollen Sie nicht (oder wie meine Mutter zu sagen pflegte: "Entblöße nicht dein Gemächt!"). Die meisten sollten Sie in der Lage sein, eine Klasse zu testen, indem Sie ihre öffentlichen Methoden. Wenn es eine wichtige Funktionalität gibt, die sich hinter privaten oder geschützten Zugängen versteckt ist, könnte das ein Warnzeichen sein, dass dass eine andere Klasse darin steckt, die darum kämpft, herauszukommen.

Aber manchmal kann ich mich nicht davon abhalten, private Methoden zu testen, weil es mir die Gewissheit gibt, dass ich eine vollständig robustes Programm.

105voto

Matt Messersmith Punkte 11353

Ich teste private Funktionen aus mehreren Gründen nicht gerne. Diese sind folgende (das sind die wichtigsten Punkte für die TLDR-Leute):

  1. Typischerweise, wenn man versucht ist, die private Methode einer Klasse zu testen, ist das ein Designgeruch.
  2. Sie können sie über die öffentliche Schnittstelle testen (was Sie auch tun sollten, denn das ist die Art und Weise, wie der Client sie aufrufen/verwenden wird). Sie können ein falsches Gefühl der Sicherheit bekommen, wenn Sie grünes Licht für alle bestandenen Tests für Ihre privaten Methoden Methoden sehen. Es ist viel besser/sicherer, die Randfälle Ihrer privaten Funktionen über Ihre öffentliche Schnittstelle zu testen.
  3. Sie riskieren eine starke Duplizierung von Tests (Tests, die sehr ähnlich aussehen/fühlen sehr ähnlich sind), wenn Sie private Methoden testen. Dies hat erhebliche Folgen, wenn sich die Anforderungen ändern, da viel mehr Tests als als notwendig. Es kann Sie auch in eine Lage bringen, in der es schwierig ist in eine Situation bringen, in der ein Refactoring aufgrund der Testsuite schwierig ist... was das ultimative Ironie des Schicksals, denn die Testsuite ist dazu da, Sie bei der sicheren Umgestaltung und Refactoring!

Ich werde jeden dieser Punkte anhand eines konkreten Beispiels erläutern. Es hat sich herausgestellt, dass 2) und 3) in gewisser Weise miteinander verbunden sind, so dass ihr Beispiel ähnlich ist, obwohl ich sie als separate Gründe betrachte, warum man private Methoden nicht testen sollte.

Es gibt Zeiten, in denen das Testen privater Methoden angebracht ist, man muss sich nur der oben genannten Nachteile bewusst sein. Ich werde später noch ausführlicher darauf eingehen.

Ich gehe auch über, warum TDD ist keine gültige Entschuldigung für das Testen von privaten Methoden ganz am Ende.

Refactoring als Ausweg aus einem schlechten Design

Eines der häufigsten (Anti-)Paternale, die ich sehe, ist das Michael Federn ruft auf. eine "Eisberg"-Klasse (Wenn Sie nicht wissen, wer Michael Feathers ist, sollten Sie sein Buch "Working Effectively with Legacy Code" kaufen/lesen. Er ist eine Person, die man kennen sollte, wenn man ein professioneller Software-Ingenieur/Entwickler ist). Es gibt noch andere (Anti-)Muster, die dieses Problem hervorrufen, aber dies ist bei weitem das häufigste, über das ich gestolpert bin. "Eisberg"-Klassen haben eine öffentliche Methode, und der Rest ist privat (weshalb es verlockend ist, die privaten Methoden zu testen). Sie wird "Eisberg"-Klasse genannt, weil in der Regel eine einzige öffentliche Methode auftaucht, während der Rest der Funktionalität in Form von privaten Methoden unter Wasser versteckt ist. Sie könnte etwa so aussehen:

Rule Evaluator

Sie könnten zum Beispiel Folgendes testen wollen GetNextToken() indem Sie es nacheinander für eine Zeichenkette aufrufen und sehen, ob es das erwartete Ergebnis liefert. Eine Funktion wie diese rechtfertigt einen Test: Dieses Verhalten ist nicht trivial, insbesondere wenn Ihre Tokenisierungsregeln komplex sind. Nehmen wir an, dass es nicht so komplex ist und wir nur Token einfügen wollen, die durch ein Leerzeichen getrennt sind. Sie schreiben also einen Test, der vielleicht so aussieht (etwas sprachunabhängiger Psuedo-Code, hoffentlich ist die Idee klar):

TEST_THAT(RuleEvaluator, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    re = RuleEvaluator(input_string);

    ASSERT re.GetNextToken() IS "1";
    ASSERT re.GetNextToken() IS "2";
    ASSERT re.GetNextToken() IS "test";
    ASSERT re.GetNextToken() IS "bar";
    ASSERT re.HasMoreTokens() IS FALSE;
}

Nun, das sieht eigentlich ganz nett aus. Wir möchten sicherstellen, dass wir dieses Verhalten beibehalten, wenn wir Änderungen vornehmen. Aber GetNextToken() es un privat Funktion! Also können wir es so nicht testen, weil es sich nicht einmal kompilieren lässt (vorausgesetzt, wir verwenden eine Sprache, die im Gegensatz zu einigen Skriptsprachen wie Python tatsächlich öffentlich/privat durchsetzt). Aber was ist mit der Änderung der RuleEvaluator Klasse das Prinzip der einzigen Verantwortung (Single-Responsibility-Prinzip) zu befolgen? Zum Beispiel scheinen wir einen Parser, einen Tokenizer und einen Evaluator in eine Klasse gepackt zu haben. Wäre es nicht besser, diese Verantwortlichkeiten einfach zu trennen? Außerdem, wenn Sie eine Tokenizer Klasse, dann wären ihre öffentlichen Methoden HasMoreTokens() et GetNextTokens() . Die RuleEvaluator Klasse könnte eine Tokenizer Objekt als Mitglied. Jetzt können wir den gleichen Test wie oben durchführen, außer dass wir die Tokenizer Klasse anstelle der RuleEvaluator Klasse.

So könnte es in UML aussehen:

Rule Evaluator Refactored

Beachten Sie, dass dieses neue Design die Modularität erhöht, so dass Sie diese Klassen möglicherweise in anderen Teilen Ihres Systems wiederverwenden können (vorher war das nicht möglich, da private Methoden per Definition nicht wiederverwendbar sind). Dies ist der Hauptvorteil der Aufgliederung von RuleEvaluator, zusammen mit der besseren Verständlichkeit/Lokalisierung.

Der Test würde sehr ähnlich aussehen, nur dass er dieses Mal tatsächlich kompiliert würde, da die GetNextToken() Methode ist jetzt öffentlich in der Tokenizer Klasse:

TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);

    ASSERT tokenizer.GetNextToken() IS "1";
    ASSERT tokenizer.GetNextToken() IS "2";
    ASSERT tokenizer.GetNextToken() IS "test";
    ASSERT tokenizer.GetNextToken() IS "bar";
    ASSERT tokenizer.HasMoreTokens() IS FALSE;
}

Testen privater Komponenten über eine öffentliche Schnittstelle und Vermeidung von Doppeltests

Selbst wenn Sie nicht glauben, dass Sie Ihr Problem in weniger modulare Komponenten zerlegen können (was Sie in 95 % der Fälle können, wenn Sie nur Versuchen Sie zu tun), können Sie die privaten Funktionen einfach über eine öffentliche Schnittstelle testen. Oft sind private Mitglieder nicht wert, getestet zu werden, weil sie über die öffentliche Schnittstelle getestet werden. Oft sehe ich Tests, die so aussehen sehr ähnlich, testen aber zwei verschiedene Funktionen/Methoden. Wenn sich die Anforderungen ändern (und das tun sie immer), haben Sie jetzt zwei fehlerhafte Tests statt einem. Und wenn Sie wirklich alle Ihre privaten Methoden getestet haben, haben Sie vielleicht sogar zehn fehlerhafte Tests statt einem. Kurz gesagt, das Testen privater Funktionen (durch Verwendung von FRIEND_TEST oder sie öffentlich zu machen oder Reflexion zu verwenden), die andernfalls über eine öffentliche Schnittstelle getestet werden könnten, können zu doppelten Tests führen . Das wollen Sie wirklich nicht, denn nichts schmerzt mehr, als wenn Ihre Testsuite Sie ausbremst. Sie soll die Entwicklungszeit verkürzen und die Wartungskosten senken! Wenn Sie private Methoden testen, die sonst über eine öffentliche Schnittstelle getestet werden, kann die Test-Suite sehr wohl das Gegenteil bewirken und die Wartungskosten und die Entwicklungszeit aktiv erhöhen. Wenn Sie eine private Funktion öffentlich machen, oder wenn Sie etwas verwenden wie FRIEND_TEST und/oder Überlegungen, werden Sie es in der Regel auf lange Sicht bereuen.

Betrachten Sie die folgende mögliche Implementierung des Tokenizer Klasse:

enter image description here

Nehmen wir an, dass SplitUpByDelimiter() ist dafür verantwortlich, ein Array zurückzugeben, bei dem jedes Element des Arrays ein Token ist. Außerdem, sagen wir einfach, dass GetNextToken() ist einfach ein Iterator über diesen Vektor. Ihr öffentlicher Test könnte also so aussehen:

TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);

    ASSERT tokenizer.GetNextToken() IS "1";
    ASSERT tokenizer.GetNextToken() IS "2";
    ASSERT tokenizer.GetNextToken() IS "test";
    ASSERT tokenizer.GetNextToken() IS "bar";
    ASSERT tokenizer.HasMoreTokens() IS false;
}

Nehmen wir einmal an, wir hätten eine, wie Michael Feather es nennt Fühlwerkzeug . Das ist ein Werkzeug, mit dem man die Geschlechtsteile anderer Menschen anfassen kann. Ein Beispiel ist FRIEND_TEST von googletest, oder Reflexion, wenn die Sprache dies unterstützt.

TEST_THAT(TokenizerTest, canGenerateSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);
    result_array = tokenizer.SplitUpByDelimiter(" ");

    ASSERT result.size() IS 4;
    ASSERT result[0] IS "1";
    ASSERT result[1] IS "2";
    ASSERT result[2] IS "test";
    ASSERT result[3] IS "bar";
}

Nun, nehmen wir an, die Anforderungen ändern sich, und die Tokenisierung wird viel komplexer. Sie entscheiden, dass ein einfaches Zeichenkettenbegrenzungszeichen nicht ausreicht, und Sie brauchen ein Delimiter Klasse, um die Aufgabe zu erledigen. Natürlich müssen Sie damit rechnen, dass ein Test fehlschlägt, aber der Schmerz nimmt zu, wenn Sie private Funktionen testen.

Wann kann es sinnvoll sein, private Methoden zu testen?

Es gibt keine "Einheitsgröße" für Software. Manchmal ist es in Ordnung (und sogar ideal), "die Regeln zu brechen". Ich empfehle dringend, private Funktionen nicht zu testen, wenn es möglich ist. Es gibt zwei Hauptsituationen, in denen ich das für in Ordnung halte:

  1. Ich habe ausgiebig mit Altsystemen gearbeitet (weshalb ich ein großer Fan von Michael Feathers bin), und ich kann mit Sicherheit sagen, dass es manchmal einfach am sichersten ist, nur die privaten Funktionen zu testen. Das kann besonders hilfreich sein, um "Charakterisierungstests" in die Baseline aufzunehmen.

  2. Sie haben es eilig und müssen im Hier und Jetzt so schnell wie möglich handeln. Auf lange Sicht wollen Sie keine privaten Methoden testen. Ich gebe aber zu bedenken, dass es in der Regel einige Zeit dauert, ein Refactoring durchzuführen, um Designprobleme zu beheben. Und manchmal muss man innerhalb einer Woche liefern. Das ist in Ordnung: Machen Sie es kurz und schmerzlos und testen Sie die privaten Methoden mit einem Tastwerkzeug, wenn Sie glauben, dass dies der schnellste und zuverlässigste Weg ist, die Aufgabe zu erledigen. Aber seien Sie sich bewusst, dass das, was Sie getan haben, auf lange Sicht suboptimal war, und überlegen Sie sich, ob Sie darauf zurückkommen wollen (oder, falls Sie es vergessen haben, aber später darauf stoßen, ob Sie es korrigieren wollen).

Es gibt wahrscheinlich auch andere Situationen, in denen das in Ordnung ist. Wenn Sie es für in Ordnung halten und eine gute Begründung dafür haben, dann tun Sie es. Niemand hält Sie davon ab. Seien Sie sich nur der möglichen Kosten bewusst.

Die TDD-Ausrede

Nebenbei bemerkt, mag ich es nicht, wenn Leute TDD als Ausrede für das Testen privater Methoden benutzen. Ich praktiziere TDD, und ich glaube nicht, dass TDD Sie zwingt, dies zu tun. Sie können Ihren Test (für Ihre öffentliche Schnittstelle) zuerst schreiben, und dann Code schreiben, um diese Schnittstelle zu erfüllen. Manchmal schreibe ich einen Test für eine öffentliche Schnittstelle, und ich werde es durch das Schreiben von ein oder zwei kleinere private Methoden als auch zu erfüllen (aber ich nicht testen die privaten Methoden direkt, aber ich weiß, dass sie funktionieren oder meine öffentliche Test wäre fehlgeschlagen). Wenn ich Randfälle dieser privaten Methode testen muss, schreibe ich eine ganze Reihe von Tests, die sie über meine öffentliche Schnittstelle ansprechen. Wenn Sie nicht herausfinden können, wie Sie die Randfälle treffen können, ist dies ein deutliches Zeichen dafür, dass Sie in kleine Komponenten mit jeweils eigenen öffentlichen Methoden umstrukturieren müssen. Es ist ein Zeichen dafür, dass Ihre privaten Funktionen zu viel tun, und außerhalb des Anwendungsbereichs der Klasse .

Außerdem stelle ich manchmal fest, dass ich einen Test schreibe, der im Moment zu groß ist, um ihn zu kauen, und dann denke ich: "Ich komme später auf diesen Test zurück, wenn ich eine größere API habe, mit der ich arbeiten kann" (ich kommentiere ihn dann aus und behalte ihn im Hinterkopf). Das ist der Punkt, an dem viele Entwickler, die ich getroffen habe, anfangen, Tests für ihre privaten Funktionen zu schreiben und TDD als Sündenbock zu benutzen. Sie sagen: "Oh, ich brauche einen anderen Test, aber um diesen Test zu schreiben, brauche ich diese privaten Methoden. Da ich also keinen produktiven Code schreiben kann, ohne einen Test zu schreiben, muss ich einen Test für eine private Methode schreiben." Was sie jedoch wirklich tun sollten, ist die Umstrukturierung in kleinere und wiederverwendbare Komponenten, anstatt einen Haufen privater Methoden zu ihrer aktuellen Klasse hinzuzufügen und zu testen.

Nota:

Ich habe eine ähnliche Frage beantwortet über Testen privater Methoden mit GoogleTest vor einiger Zeit. Ich habe diese Antwort größtenteils abgeändert, um hier mehr sprachunabhängig zu sein.

P.S. Hier ist der entsprechende Vortrag von Michael Feathers über Eisbergklassen und Tastwerkzeuge: https://www.youtube.com/watch?v=4cVZvoFGJTU

67voto

VonC Punkte 1117238

Ich fühle mich irgendwie gezwungen, private Funktionen zu testen, da ich mehr und mehr einer unserer letzten QA-Empfehlungen in unserem Projekt folge:

Nicht mehr als 10 in zyklomatische Komplexität pro Funktion.

Der Nebeneffekt der Durchsetzung dieser Politik ist nun, dass viele meiner sehr großen öffentlichen Funktionen in viele gezieltere, besser benannte Funktionen aufgeteilt werden privat Funktion.
Die öffentliche Funktion ist natürlich immer noch vorhanden, wird aber im Wesentlichen darauf reduziert, all diese privaten "Unterfunktionen" aufzurufen

Das ist eigentlich cool, weil der Callstack jetzt viel einfacher zu lesen ist (statt eines Fehlers in einer großen Funktion, habe ich einen Fehler in einer Sub-Sub-Funktion mit dem Namen der vorherigen Funktionen im Callstack, um mir zu helfen, zu verstehen 'wie ich dorthin kam')

Allerdings scheint es jetzt einfacher zu sein, diese Einheiten direkt zu testen privat Funktionen, und überlassen Sie das Testen der großen öffentlichen Funktion einer Art "Integrationstest", bei dem ein Szenario behandelt werden muss.

Nur meine 2 Cents.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X