Ich mag es aus mehreren Gründen nicht, private Funktionen zu testen. Sie sind wie folgt (dies sind die Hauptpunkte für die TLDR-Leute):
- Wenn Sie versucht sind, die private Methode einer Klasse zu testen, ist dies normalerweise ein Designgeruch.
- Sie können sie über die öffentliche Schnittstelle testen (so möchten Sie sie testen, da der Client sie so aufruft / verwendet). Sie können ein falsches Sicherheitsgefühl bekommen, indem Sie grünes Licht für alle bestandenen Tests für Ihre privaten Methoden sehen. Es ist viel besser / sicherer, Edge Cases für Ihre privaten Funktionen über Ihre öffentliche Schnittstelle zu testen.
- Sie riskieren schwere Testduplikationen (Tests, die sehr ähnlich aussehen / sich sehr ähnlich anfühlen), indem Sie private Methoden testen. Dies hat erhebliche Konsequenzen, wenn sich die Anforderungen ändern, da viel mehr Tests als erforderlich unterbrochen werden. Es kann Sie auch in eine Position bringen, in der es aufgrund Ihrer Testsuite schwierig ist, eine Umgestaltung vorzunehmen ... was die ultimative Ironie ist, da die Testsuite Ihnen dabei hilft, sicher neu zu gestalten und umzugestalten!
Ich werde jedes davon mit einem konkreten Beispiel erklären. Es stellt sich heraus, dass 2) und 3) etwas eng miteinander verbunden sind, so dass ihr Beispiel ähnlich ist, obwohl ich sie als separate Gründe betrachte, warum Sie private Methoden nicht testen sollten.
Es ist manchmal angebracht, private Methoden zu testen. Es ist nur wichtig, sich der oben aufgeführten Nachteile bewusst zu sein. Ich werde später genauer darauf eingehen.
Ich gehe auch darauf ein, warum TDD am Ende keine gültige Entschuldigung für das Testen privater Methoden ist.
Umgestaltung Ihres Weges aus einem schlechten Design
Eines der häufigsten (Anti) Muster, das ich sehe, ist das, was Michael Feathers eine "Eisberg" -Klasse nennt (wenn Sie nicht wissen, wer Michael Feathers ist, kaufen / lesen Sie sein Buch "Effektiv mit Legacy-Code arbeiten" eine wissenswerte Person, wenn Sie ein professioneller Softwareentwickler / -entwickler sind). Es gibt andere (Anti) Muster, die dazu führen, dass dieses Problem auftritt, aber dies ist bei weitem das häufigste, über das ich gestolpert bin. "Iceberg" -Klassen haben eine öffentliche Methode, und der Rest ist privat (weshalb es verlockend ist, die privaten Methoden zu testen). Es wird als "Eisberg" -Klasse bezeichnet, da normalerweise eine einzelne öffentliche Methode auftaucht, der Rest der Funktionalität jedoch in Form privater Methoden unter Wasser verborgen ist.
Beispielsweise möchten Sie möglicherweise testen, GetNextToken()
indem Sie eine Zeichenfolge nacheinander aufrufen und feststellen, dass das erwartete Ergebnis zurückgegeben wird. Eine solche Funktion erfordert einen Test: Dieses Verhalten ist nicht trivial, insbesondere wenn Ihre Tokenisierungsregeln komplex sind. Stellen wir uns vor, es sei nicht allzu komplex, und wir wollen nur Token einbinden, die durch den Raum begrenzt sind. Sie schreiben also einen Test, vielleicht sieht er ungefähr so aus (ein sprachunabhängiger Pseudocode, 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 gut aus. Wir möchten sicherstellen, dass wir dieses Verhalten beibehalten, wenn wir Änderungen vornehmen. Ist GetNextToken()
aber eine private Funktion! Daher können wir es nicht so testen, da es nicht einmal kompiliert wird (vorausgesetzt, wir verwenden eine Sprache, die im Gegensatz zu einigen Skriptsprachen wie Python tatsächlich öffentlich / privat erzwingt). Aber wie wäre es, die RuleEvaluator
Klasse so zu ändern , dass sie dem Prinzip der Einzelverantwortung (Prinzip der Einzelverantwortung) folgt? Zum Beispiel scheinen Parser, Tokenizer und Evaluator in einer Klasse zusammengefasst zu sein. Wäre es nicht besser, diese Verantwortlichkeiten einfach zu trennen? Wenn Sie eine Tokenizer
Klasse erstellen , sind die öffentlichen Methoden außerdem HasMoreTokens()
und GetNextTokens()
. Die RuleEvaluator
Klasse könnte eine habenTokenizer
Objekt als Mitglied. Jetzt können wir den gleichen Test wie oben beibehalten, außer dass wir die Tokenizer
Klasse anstelle der RuleEvaluator
Klasse testen .
So könnte es in UML aussehen:
Beachten Sie, dass dieses neue Design die Modularität erhöht, sodass Sie diese Klassen möglicherweise in anderen Teilen Ihres Systems wiederverwenden können (bevor Sie dies nicht konnten, können private Methoden per Definition nicht wiederverwendet werden). Dies ist der Hauptvorteil des Ausfalls des RuleEvaluator zusammen mit einer besseren Verständlichkeit / Lokalität.
Der Test würde sehr ähnlich aussehen, außer dass er diesmal tatsächlich kompiliert würde, da die GetNextToken()
Methode jetzt für die Tokenizer
Klasse öffentlich ist :
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 Vermeiden von Testduplikationen
Selbst wenn Sie nicht glauben, dass Sie Ihr Problem in weniger modulare Komponenten aufteilen können (was Sie in 95% der Fälle tun können, wenn Sie es nur versuchen ), können Sie die privaten Funktionen einfach über eine öffentliche Schnittstelle testen. Oft sind private Mitglieder keinen Test wert, da sie über die öffentliche Schnittstelle getestet werden. Oft sehe ich Tests, die aussehen sehr gut ähnlich , aber zwei verschiedene Funktionen / Methoden testen. Was am Ende passiert, ist, dass Sie, wenn sich die Anforderungen ändern (und dies immer tun), jetzt 2 fehlerhafte Tests anstelle von 1 haben. Und wenn Sie wirklich alle Ihre privaten Methoden getestet haben, haben Sie möglicherweise eher 10 fehlerhafte Tests anstelle von 1. Kurz gesagt Testen privater Funktionen (mithilfe vonFRIEND_TEST
oder sie öffentlich zu machen oder Reflexion zu verwenden), die andernfalls über eine öffentliche Schnittstelle getestet werden könnten, kann zu Testduplikationen führen und / oder Reflexion verwenden, werden Sie es auf lange Sicht normalerweise bereuen.. Sie wollen das wirklich nicht, denn nichts tut mehr weh als Ihre Testsuite, die Sie verlangsamt. Es soll die Entwicklungszeit verkürzen und die Wartungskosten senken! Wenn Sie private Methoden testen, die ansonsten über eine öffentliche Schnittstelle getestet werden, kann die Testsuite genau das Gegenteil bewirken und die Wartungskosten und die Entwicklungszeit aktiv erhöhen. Wenn Sie eine private Funktion öffentlich machen oder wenn Sie so etwas verwendenFRIEND_TEST
Betrachten Sie die folgende mögliche Implementierung der Tokenizer
Klasse:
Angenommen, dies SplitUpByDelimiter()
ist für die Rückgabe eines Arrays verantwortlich, sodass jedes Element im Array ein Token ist. Sagen wir einfach, das 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;
}
Stellen wir uns vor, wir hätten das, was Michael Feather als tastendes Werkzeug bezeichnet . Mit diesem Tool können Sie die privaten Bereiche anderer Personen berühren. Ein Beispiel ist FRIEND_TEST
von googletest oder Reflexion, wenn die Sprache es 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";
}
Nehmen wir jetzt an, die Anforderungen ändern sich und die Tokenisierung wird viel komplexer. Sie entscheiden, dass ein einfaches Zeichenfolgenbegrenzer nicht ausreicht, und Sie benötigen einDelimiter
Klasse, um den Job zu erledigen. Natürlich erwarten Sie, dass ein Test abbricht, aber dieser Schmerz nimmt zu, wenn Sie private Funktionen testen.
Wann können private Methoden getestet werden?
In der Software gibt es keine "Einheitsgröße". Manchmal ist es in Ordnung (und eigentlich ideal), "die Regeln zu brechen". Ich empfehle nachdrücklich, private Funktionen nicht zu testen, wenn Sie können. Es gibt zwei Hauptsituationen, in denen ich denke, dass es in Ordnung ist:
Ich habe ausgiebig mit Legacy-Systemen gearbeitet (weshalb ich so ein großer Michael Feathers-Fan bin), und ich kann mit Sicherheit sagen, dass es manchmal einfach am sichersten ist, nur die private Funktionalität zu testen. Dies kann besonders hilfreich sein, um "Charakterisierungstests" in die Grundlinie aufzunehmen.
Sie sind in Eile und müssen das Schnellste tun, was hier und jetzt möglich ist. Auf lange Sicht möchten Sie keine privaten Methoden testen. Aber ich werde sagen, dass es normalerweise einige Zeit dauert, um Designprobleme zu beheben. Und manchmal muss man in einer Woche versenden. Das ist in Ordnung: Machen Sie es schnell und schmutzig und testen Sie die privaten Methoden mit einem tastenden Werkzeug, wenn dies Ihrer Meinung nach der schnellste und zuverlässigste Weg ist, um die Arbeit zu erledigen. Aber verstehen Sie, dass das, was Sie getan haben, auf lange Sicht nicht optimal war, und denken Sie darüber nach, darauf zurückzukommen (oder, wenn es vergessen wurde, aber Sie es später sehen, beheben Sie es).
Es gibt wahrscheinlich andere Situationen, in denen es in Ordnung ist. Wenn Sie denken, dass es in Ordnung ist und Sie eine gute Rechtfertigung haben, dann tun Sie es. Niemand hält dich auf. Seien Sie sich nur der möglichen Kosten bewusst.
Die TDD-Entschuldigung
Abgesehen davon mag ich es wirklich nicht, wenn Leute TDD als Ausrede für das Testen privater Methoden verwenden. Ich übe TDD und ich glaube nicht, dass TDD Sie dazu zwingt. Sie können zuerst Ihren Test (für Ihre öffentliche Schnittstelle) schreiben und dann Code schreiben, um diese Schnittstelle zu erfüllen. Manchmal schreibe ich einen Test für eine öffentliche Schnittstelle und erfülle ihn, indem ich auch eine oder zwei kleinere private Methoden schreibe (aber ich teste die privaten Methoden nicht direkt, aber ich weiß, dass sie funktionieren oder mein öffentlicher Test fehlschlagen würde ). Wenn ich Randfälle dieser privaten Methode testen muss, schreibe ich eine ganze Reihe von Tests, die sie über meine öffentliche Schnittstelle treffen.Wenn Sie nicht herausfinden können, wie Sie die Randfälle treffen können, ist dies ein starkes Zeichen dafür, dass Sie kleine Komponenten mit jeweils eigenen öffentlichen Methoden umgestalten müssen. Es ist ein Zeichen dafür, dass Ihre privaten Funktionen zu viel tun und außerhalb des Bereichs der Klasse liegen .
Manchmal finde ich auch, dass ich einen Test schreibe, der im Moment zu groß ist, um ihn zu kauen, und deshalb denke ich, "eh, ich werde später auf diesen Test zurückkommen, wenn ich mehr API zum Arbeiten habe" (I. Ich werde es auskommentieren und im Hinterkopf behalten. Hier werden viele Entwickler, die ich getroffen habe, dann Tests für ihre private Funktionalität schreiben und TDD als Sündenbock verwenden. Sie sagen: "Oh, nun, ich brauche einen anderen Test, aber um diesen Test zu schreiben, brauche ich diese privaten Methoden. Da ich keinen Produktionscode schreiben kann, ohne einen Test zu schreiben, muss ich einen Test schreiben für eine private Methode. " Was sie jedoch wirklich tun müssen, ist die Umgestaltung in kleinere und wiederverwendbare Komponenten, anstatt ihrer aktuellen Klasse eine Reihe privater Methoden hinzuzufügen / zu testen.
Hinweis:
Ich habe vor einiger Zeit eine ähnliche Frage zum Testen privater Methoden mit GoogleTest beantwortet . Ich habe diese Antwort größtenteils geändert, um hier sprachunabhängiger zu sein.
PS Hier ist die relevante Vorlesung über Eisbergklassen und Greifwerkzeuge von Michael Feathers: https://www.youtube.com/watch?v=4cVZvoFGJTU