Manchmal sind private Funktionen einfach noch zu extrahierende interne Funktionseinheiten. Warum also nicht testen?

9

Manchmal sind private Funktionen eines Moduls oder einer Klasse einfach noch zu extrahierende interne Funktionseinheiten, die möglicherweise ihre eigenen Tests verdienen. Warum also nicht testen? Wir werden später Tests für sie schreiben, wenn sie extrahiert werden. Warum also nicht jetzt die Tests schreiben, wenn sie noch Teil derselben Datei sind?

Demonstrieren:

Geben Sie hier die Bildbeschreibung ein

Zuerst habe ich geschrieben module_a. Jetzt möchte ich Tests dafür schreiben. Ich möchte die 'private' Funktion testen _private_func. Ich verstehe nicht, warum ich keinen Test dafür schreiben würde, wenn ich ihn später ohnehin in ein eigenes internes Modul umgestalten und dann Tests dafür schreiben könnte.


Angenommen, ich habe ein Modul mit den folgenden Funktionen (es könnte auch eine Klasse sein):

def public_func(a):
    b = _do_stuff(a)
    return _do_more_stuff(b)

_do_stuffund _do_more_stuffsind "private" Funktionen des Moduls.

Ich verstehe die Idee, dass wir nur die öffentliche Schnittstelle testen sollten, nicht die Implementierungsdetails. Hier ist jedoch die Sache:

_do_stuffund _do_more_stuffenthalten den größten Teil der Funktionalität des Moduls. Jeder von ihnen könnte eine öffentliche Funktion eines anderen "internen" Moduls sein. Sie sind jedoch noch nicht weiterentwickelt und groß genug, um in separate Dateien extrahiert zu werden.

Das Testen dieser Funktionen fühlt sich also richtig an, da sie wichtige Funktionseinheiten sind. Wenn sie als öffentliche Funktionen in verschiedenen Modulen wären, hätten wir sie getestet. Warum testen Sie sie nicht, wenn sie noch nicht (oder noch nie) in eine andere Datei extrahiert wurden?

Aviv Cohn
quelle
2
"Private Methoden sind für Unit-Tests von Vorteil ..." "... das Festhalten an privaten Methoden bringt mir eine nützliche und zuverlässige Verbesserung bei Unit-Tests. Im Gegensatz dazu gibt mir die Schwächung der Zugriffsbeschränkungen" für Testbarkeit "nur eine dunkle, schwer verständliche Stück Testcode, der zusätzlich permanent gefährdet ist, durch kleinere Umgestaltungen beschädigt zu werden; ehrlich gesagt sieht das, was ich bekomme, verdächtig nach technischer Verschuldung aus "
Mücke
3
"Soll ich private Funktionen testen?" Nein, niemals, niemals.
David Arno
2
@ DavidArno Warum? Was ist die Alternative zum Testen von Interna? Nur Integrationstests? Oder mehr Dinge öffentlich machen? (obwohl ich meiner Erfahrung nach meistens öffentliche Methoden an internen Klassen
teste
1
Wenn es wichtig genug ist, dass Tests dafür geschrieben werden müssen, sollte es sich bereits in einem separaten Modul befinden. Wenn dies nicht der Fall ist, testen Sie das Verhalten mithilfe der öffentlichen API.
Vincent Savard

Antworten:

14

Das Bedürfnis zu testen ist nicht dasselbe wie das Bedürfnis, öffentlich zu sein.

Nicht trivialer Code muss unabhängig von der Exposition getestet werden. Nicht öffentliches Verhalten muss nicht existieren, geschweige denn getestet werden.

Diese widersprüchlichen Ansichten können dazu führen, dass Sie jede Funktion öffentlich machen oder sich weigern möchten, Code in eine Funktion zu zerlegen, es sei denn, er ist öffentlich.

Dies ist nicht die Antwort. Seien Sie bereit, private Hilfsfunktionen zu erstellen. Testen Sie sie über die öffentliche Schnittstelle, die sie so oft wie möglich verwendet.

Wenn das Testen über die öffentliche Schnittstelle die private Funktion nicht ausreichend ausübt, versucht die private Funktion zu viel zuzulassen.

Die Validierung kann dazu beitragen, die Möglichkeiten der privaten Funktion einzugrenzen. Wenn Sie keine Null übergeben können, die die öffentliche Schnittstelle durchläuft, können Sie trotzdem eine Ausnahme auslösen, wenn eine trotzdem durchkommt.

Warum solltest du? Warum testen, was Sie nie sehen werden? Weil sich die Dinge ändern. Es kann jetzt privat sein, aber später öffentlich sein. Der aufrufende Code könnte sich ändern. Code, der null explizit ablehnt, macht die ordnungsgemäße Verwendung und den erwarteten Status deutlich.

Natürlich könnte null in Ordnung sein. Es ist nur ein Beispiel hier. Aber wenn Sie etwas erwarten, ist es nützlich, diese Erwartung klar zu machen.

Dies ist möglicherweise nicht die Art von Test, an die Sie gedacht haben, aber hoffentlich sind Sie bereit, bei Bedarf private Hilfsfunktionen zu erstellen.

Der Wunsch zu testen ist gut, sollte aber nicht die treibende Kraft beim Design Ihrer öffentlichen API sein. Entwerfen Sie die öffentliche API so, dass sie einfach zu verwenden ist. Es wird wahrscheinlich nicht sein, wenn jede Funktion öffentlich ist. Die API sollte etwas sein, das die Benutzer verstehen können, ohne in den Code einzutauchen. Lassen Sie solche Leute nicht fragen, wofür diese seltsame Hilfsfunktion gedacht ist.

Das Ausblenden öffentlicher Hilfsfunktionen in einem internen Modul ist ein Versuch, die Notwendigkeit einer sauberen API zu berücksichtigen und gleichzeitig Helfer zum Testen bereitzustellen. Ich werde nicht sagen, dass das falsch ist. Möglicherweise machen Sie den ersten Schritt in Richtung einer anderen Architekturebene. Aber bitte beherrschen Sie die Kunst, private Hilfsfunktionen durch die öffentlichen Funktionen zu testen, die sie zuerst verwenden. Auf diese Weise werden Sie diese Problemumgehung nicht zu häufig verwenden.

candied_orange
quelle
Ich habe einen Ansatz gefunden, ich würde gerne Ihre Meinung hören: Wenn ich in einer Situation bin, in der ich eine private Funktion testen möchte, werde ich prüfen, ob ich sie durch eine der öffentlichen Funktionen ausreichend testen kann (einschließlich aller Randfälle usw.). Wenn ich kann, werde ich keinen Test für diese Funktion speziell schreiben, sondern ihn nur durch Testen der öffentlichen Funktionen testen, die ihn verwenden. Wenn ich jedoch der Meinung bin, dass die Funktion über die öffentliche Schnittstelle nicht ausreichend getestet werden kann und einen eigenen Test verdient, werde ich sie in ein internes Modul extrahieren, in dem sie öffentlich ist, und Tests dafür schreiben. Was denkst du?
Aviv Cohn
Ich werde sagen, ich frage das Gleiche wie die anderen, die hier geantwortet haben :) Ich würde gerne die Meinung aller hören.
Aviv Cohn
Wieder werde ich dir nicht nein sagen. Ich mache mir Sorgen, dass Sie nichts darüber gesagt haben, wie sich dies auf die Benutzerfreundlichkeit auswirkt. Der Unterschied zwischen öffentlich und privat ist nicht strukturell. Es ist Gebrauch. Wenn der Unterschied zwischen öffentlich und privat eine Vordertür und eine Hintertür ist, besteht Ihre Aufgabe darin, einen Schuppen im Hinterhof zu bauen. Fein. Solange sich die Leute dort nicht verlaufen.
candied_orange
1
Upvoted für "Wenn das Testen über die öffentliche Schnittstelle die private Funktion nicht ausreichend ausübt, versucht die private Funktion, zu viel zuzulassen."
Kris Welsh
7

Kurze Antwort: Nein

Längere Antwort: Ja, aber über die öffentliche 'API' Ihrer Klasse

Die ganze Idee von privaten Mitgliedern einer Klasse ist, dass sie Funktionen darstellen, die außerhalb der 'Codeeinheit' unsichtbar sein sollten, egal wie groß Sie diese Einheit definieren möchten. Im objektorientierten Code wird diese Einheit häufig zu einer Klasse.

Sie sollten Ihre Klasse so gestalten, dass es möglich ist, alle privaten Funktionen über verschiedene Kombinationen von Eingabestatus aufzurufen. Wenn Sie feststellen, dass es keinen relativ einfachen Weg gibt, dies zu tun, deutet dies wahrscheinlich darauf hin, dass Ihr Design genauer betrachtet werden muss.


Nach Klärung der Frage ist dies nur eine Frage der Semantik. Wenn der betreffende Code als separate eigenständige Einheit arbeiten kann und getestet wird, als wäre er öffentlicher Code, kann ich keinen Vorteil darin sehen, ihn nicht in ein eigenständiges Modul zu verschieben. Gegenwärtig dient es nur dazu, zukünftige Entwickler (einschließlich Sie selbst in 6 Monaten) zu verwirren, warum der scheinbar öffentliche Code in einem anderen Modul versteckt ist.

Richzilla
quelle
Hallo, danke für deine Antwort :) Bitte lies die Frage noch einmal, die ich zur Klärung bearbeitet habe.
Aviv Cohn
Ich habe einen Ansatz gefunden, ich würde gerne Ihre Meinung hören: Wenn ich in einer Situation bin, in der ich eine private Funktion testen möchte, werde ich prüfen, ob ich sie durch eine der öffentlichen Funktionen ausreichend testen kann (einschließlich aller Randfälle usw.). Wenn ich kann, werde ich keinen Test für diese Funktion speziell schreiben, sondern ihn nur durch Testen der öffentlichen Funktionen testen, die ihn verwenden. Wenn ich jedoch der Meinung bin, dass die Funktion über die öffentliche Schnittstelle nicht ausreichend getestet werden kann und einen eigenen Test verdient, werde ich sie in ein internes Modul extrahieren, in dem sie öffentlich ist, und Tests dafür schreiben. Was denkst du?
Aviv Cohn
Ich werde sagen, ich frage das Gleiche wie die anderen, die hier geantwortet haben :) Ich würde gerne die Meinung aller hören.
Aviv Cohn
5

Der springende Punkt bei privaten Funktionen ist, dass es sich um versteckte Implementierungsdetails handelt, die nach Belieben geändert werden können, ohne die öffentliche API zu ändern. Für Ihren Beispielcode:

def public_func(a):
    b = _do_stuff(a)
    return _do_more_stuff(b)

Wenn Sie eine Reihe von Tests haben, die nur verwendet werden public_func, schreiben Sie sie wie folgt um:

def public_func(a):
    b = _do_all_the_new_stuff(a)
    return _do_all_the_old_stuff(b)

Solange das Rückgabeergebnis für einen bestimmten Wert von gleich ableibt, sind alle Ihre Tests gut. Wenn sich das Rückgabeergebnis ändert, schlägt ein Test fehl und zeigt, dass etwas kaputt gegangen ist.

Dies ist alles eine gute Sache: statische öffentliche API; gut eingekapseltes Innenleben; und robuste Tests.

Wenn Sie jedoch Tests für _do_stuffoder geschrieben _do_more_stuffund dann die oben genannte Änderung vorgenommen haben, haben Sie jetzt eine Reihe fehlerhafter Tests, nicht weil sich die Funktionalität geändert hat, sondern weil sich die Implementierung dieser Funktionalität geändert hat. Diese Tests müssten neu geschrieben werden, um mit den neuen Funktionen zu arbeiten. Wenn Sie sie jedoch zum Laufen gebracht haben, wissen Sie nur, dass diese Tests mit den neuen Funktionen funktionieren. Sie hätten die ursprünglichen Tests verloren und würden daher nicht wissen, ob sich das Verhalten von public_funcgeändert hat, und daher wären Ihre Tests im Grunde genommen nutzlos.

Dies ist eine schlechte Sache: eine sich ändernde API; freiliegendes Innenleben eng mit Tests verbunden; und spröde Tests, die sich ändern, sobald Änderungen an der Implementierung vorgenommen werden.

Also nein, testen Sie keine privaten Funktionen. Je.

David Arno
quelle
Hallo David, danke für deine Antwort. Bitte lesen Sie meine Frage noch einmal, die ich zur Klärung bearbeitet habe.
Aviv Cohn
Meh. Es ist nichts Falsches daran, interne Funktionen zu testen. Ich persönlich schreibe keine nicht triviale Funktion, es sei denn, ich kann sie mit ein paar Unit-Tests abdecken. Ein Verbot von Tests für interne Funktionen würde mich also so gut wie daran hindern, interne Funktionen zu schreiben, und mich einer sehr nützlichen Technik berauben. APIs können und müssen sich ändern. Wenn dies der Fall ist, müssen Sie die Tests ändern. Das Refactoring einer inneren Funktion (und ihres Tests) unterbricht nicht die Tests der äußeren Funktion. Das ist der springende Punkt bei diesen Tests. Schlechter Rat insgesamt.
Robert Harvey
Was Sie wirklich argumentieren, ist, dass Sie keine privaten Funktionen haben sollten.
Robert Harvey
1
@AvivCohn Sie sind entweder groß genug, um Tests zu rechtfertigen. In diesem Fall sind sie groß genug, um ihre eigene Datei zu erhalten. oder sie sind klein genug, dass Sie sie nicht separat testen müssen. Also was ist es?
Doval
3
@RobertHarvey Nein, es wird das Argument "große Klassen bei Bedarf in lose gekoppelte Komponenten zerlegen". Wenn sie wirklich nicht öffentlich sind, ist dies wahrscheinlich ein guter Anwendungsfall für die paketprivate Sichtbarkeit.
Doval