Wie kann ich Unit-Tests für privaten Code befürworten?

15

Ich versuche, Unit-Tests in meiner Arbeitsgruppe zu befürworten, aber ich habe oft den Einwand, dass sie nur für extern exportierte APIs (die nur einen minimalen und nicht kritischen Teil unseres Systems darstellen) und nicht für interne und private APIs verwendet werden sollten Code (der jetzt nur Funktionstests hat).

Wie kann ich meine Kollegen davon überzeugen, dass der Komponententest auf den gesamten Code angewendet werden kann und sollte?

Wizard79
quelle
3
Wenn Sie über private Methoden verfügen, die Sie testen möchten, ist dies oft ein Zeichen dafür, dass Ihr Code gegen die SRP verstößt, und es gibt eine andere Klasse, die darauf schreit, extrahiert und eigenständig getestet zu werden.
Paddyslacker
@Paddyslacker: Ich bin der Meinung, dass der gesamte Code getestet werden muss. Ich verstehe nicht, warum eine Code-Einheit, die dem Prinzip der Einzelverantwortung folgt, keinen Einheitentests unterzogen werden sollte ...
Wizard79
4
@lorenzo, du hast meinen Standpunkt verpasst; Vielleicht habe ich es nicht sehr gut gemacht. Wenn Sie diese privaten Methoden in eine andere Klasse extrahieren, müssen sie jetzt in Ihrer ursprünglichen Klasse verfügbar sein. Da die Methoden jetzt öffentlich sind, müssen sie getestet werden. Ich habe nicht angedeutet, dass sie nicht getestet werden sollten. Ich habe angedeutet, dass, wenn Sie das Bedürfnis haben, die Methoden direkt zu testen, es wahrscheinlich ist, dass sie nicht privat sein sollten.
Paddyslacker
@Paddyslacker: Ich habe das Bedürfnis, auch private Methoden direkt zu testen. Warum denkst du, dass sie nicht privat sein sollten?
Wizard79
6
Indem Sie private Methoden testen, brechen Sie die Abstraktion. Sie sollten den Status und / oder das Verhalten in Komponententests testen, nicht die Implementierung. Ihre Beispiele / Szenarien sollten in der Lage sein zu überprüfen, was das Ergebnis des privaten Codes ist - wenn Sie das schwierig finden, könnte dies, wie Paddyslacker sagt, bedeuten, dass Sie SRP verletzen. Es könnte auch bedeuten, dass Sie Ihre Beispiele nicht destilliert haben, um wirklich repräsentativ für das zu sein, was Ihr Code tut.
FinnNk

Antworten:

9

Ihre Mitarbeiter verwechseln möglicherweise echte Komponententests mit Integrationstests. Wenn Ihr Produkt eine API ist (oder hat), können die Integrationstests als NUnit-Testfälle programmiert werden. Einige Leute glauben fälschlicherweise, dass dies Unit-Tests sind.

Sie können versuchen, Ihre Mitarbeiter mit den folgenden Informationen zu überzeugen (ich bin sicher, dass Sie diese Informationen bereits kennen. Ich sage nur, dass es hilfreich sein kann, wenn Sie Ihre Kollegen darauf hinweisen):

  • Testabdeckung . Messen Sie den tatsächlichen Prozentsatz der Testabdeckung dieser Integrationstests. Dies ist ein Reality-Check für diejenigen, die noch nie einen Test durchgeführt haben. Da es schwierig ist, alle logischen Pfade auszuüben, wenn die Eingabe mehrere Ebenen entfernt ist, liegt die Testabdeckung zwischen 20% und 50%. Um mehr Berichterstattung zu erhalten, müssen Ihre Mitarbeiter echte, isolierte Komponententests schreiben.
  • Konfiguration . Wenn Sie dieselbe getestete Software bereitstellen, können Sie Ihren Mitarbeitern möglicherweise demonstrieren, wie schwierig es ist, ihre Tests in einer anderen Umgebung auszuführen. Pfade zu verschiedenen Dateien, DB-Verbindungszeichenfolgen, URLs von Remotediensten usw. - alles summiert sich.
  • Ausführungszeit . Sofern die Tests keine echten Komponententests sind und im Speicher ausgeführt werden können, dauert die Ausführung viel Zeit.
Azheglov
quelle
12

Die Gründe für die Verwendung von Komponententests für internen / privaten Code sind genau die gleichen wie für extern unterstützte APIs:

  • Sie verhindern, dass sich Fehler wiederholen (Komponententests sind Teil Ihrer Regressionstestsuite).
  • Sie dokumentieren (in einem ausführbaren Format!), Dass der Code funktioniert.
  • Sie bieten eine ausführbare Definition dessen, was "der Code funktioniert" bedeutet.
  • Sie bieten ein automatisiertes Mittel, um zu demonstrieren, dass der Code tatsächlich mit den Spezifikationen übereinstimmt (wie durch den obigen Punkt definiert).
  • Sie zeigen, wie die Unit / Klasse / Modul / Funktion / Methode bei unerwarteten Eingaben versagt.
  • Sie enthalten Beispiele für die Verwendung des Geräts. Dies ist eine hervorragende Dokumentation für neue Teammitglieder.
Frank Shearar
quelle
8

Wenn Sie privat meinen, wie ich es glaube, dann nein - Sie sollten es nicht als Unit testen. Sie sollten nur beobachtbare Verhaltensweisen / Zustände testen. Möglicherweise fehlt der Punkt hinter dem "Rot-Grün-Refaktor" -Zyklus von TDD (und wenn Sie nicht zuerst testen, gilt dasselbe Prinzip). Sobald die Tests geschrieben sind und bestanden wurden, möchten Sie nicht, dass sie sich während des Refactorings ändern. Wenn Sie gezwungen sind, die private Funktionalität einer Unit zu testen, bedeutet dies wahrscheinlich, dass die Unit-Tests in Bezug auf die öffentliche Funktionalität fehlerhaft sind. Wenn es schwierig und komplex ist, Tests für den öffentlichen Code zu schreiben, tut Ihre Klasse möglicherweise zu viel oder Ihr Problem ist nicht klar definiert.

Schlimmer noch, im Laufe der Zeit werden Ihre Komponententests zu einer Kugel und einer Kette, die Sie verlangsamen, ohne irgendeinen Wert hinzuzufügen (eine Änderung der Implementierung, beispielsweise die Optimierung oder das Entfernen von Duplikaten, sollte sich nicht auf die Komponententests auswirken). Interner Code sollte jedoch Unit-getestet werden, da das Verhalten / der Zustand (nur eingeschränkt) beobachtet werden kann.

Als ich zum ersten Mal Unit-Tests durchgeführt habe, habe ich alle möglichen Tricks angewendet, um private Dinge zu testen. Jetzt, da ich ein paar Jahre hinter mir habe, sehe ich das als schlimmer als Zeitverschwendung an.

Hier ist ein dummes Beispiel, natürlich hätten Sie im wirklichen Leben mehr Tests als diese:

Angenommen, Sie haben eine Klasse, die eine sortierte Liste von Zeichenfolgen zurückgibt. Sie sollten überprüfen, ob das Ergebnis sortiert ist und nicht, wie diese Liste tatsächlich sortiert wird. Sie können Ihre Implementierung mit einem einzelnen Algorithmus starten, der nur die Liste sortiert. Sobald dies erledigt ist, muss sich Ihr Test nicht mehr ändern, wenn Sie dann Ihren Sortieralgorithmus ändern. Zu diesem Zeitpunkt haben Sie einen einzelnen Test (vorausgesetzt, die Sortierung ist in Ihre Klasse eingebettet):

  1. Ist mein Ergebnis sortiert?

Angenommen, Sie möchten zwei Algorithmen (vielleicht ist einer unter bestimmten Umständen effizienter, andere jedoch nicht), dann könnte (und sollte) jeder Algorithmus von einer anderen Klasse bereitgestellt werden und Ihre Klasse wählt sie aus - Sie können überprüfen, ob dies geschieht Ihre gewählten Szenarien verwenden Mocks, aber Ihr ursprünglicher Test ist immer noch gültig und da wir nur beobachtbares Verhalten / Zustand überprüfen, muss er sich nicht ändern. Sie erhalten 3 Tests:

  1. Ist mein Ergebnis sortiert?
  2. In einem gegebenen Szenario (sagen wir, die anfängliche Liste ist von Anfang an fast sortiert) wird die Klasse aufgerufen, die Zeichenfolgen mit dem Algorithmus X sortiert.
  3. In einem gegebenen Szenario (die anfängliche Liste ist in zufälliger Reihenfolge) wird die Klasse aufgerufen, die Zeichenfolgen mit dem Algorithmus Y sortiert.

Die Alternative wäre gewesen, mit dem Testen von privatem Code in Ihrer Klasse zu beginnen - Sie haben nichts davon - die obigen Tests sagen mir alles, was ich zum Testen von Einheiten wissen muss. Wenn Sie private Tests hinzufügen, bauen Sie sich eine Zwangsjacke. Wie viel mehr Arbeit wäre es, wenn Sie nicht nur überprüfen würden, ob das Ergebnis sortiert wurde, sondern auch, wie es sortiert ist?

Tests (dieses Typs) sollten sich nur ändern, wenn sich das Verhalten ändert. Beginnen Sie mit dem Schreiben von Tests für privaten Code und das geht aus dem Fenster.

FinnNk
quelle
1
Vielleicht gibt es ein Missverständnis über die Bedeutung von "privat". In unserem System sind 99% des Codes "privat", dann haben wir eine kleine API zur Automatisierung / Fernsteuerung einer der Komponenten des Systems. Ich meine das Testen des Codes aller anderen Module.
Wizard79
4

Hier ist ein weiterer Grund: Im hypothetischen Fall müsste ich zwischen Unit-Tests der externen API und den privaten Teilen wählen, ich würde die privaten Teile wählen.

Wenn jeder private Teil durch einen Test abgedeckt wird, sollte die API, die aus diesen privaten Teilen besteht, ebenfalls fast zu 100% abgedeckt werden, ausgenommen nur für die obere Schicht. Das dürfte aber eine dünne Schicht sein.

Andererseits kann es beim Testen der API sehr schwierig sein, alle möglichen Codepfade vollständig abzudecken.

stijn
quelle
+1 "auf der anderen Seite ..." Aber wenn nichts anderes, fügen Sie Tests hinzu, bei denen ein Fehler am meisten schaden würde.
Tony Ennis
2

Es ist schwierig, die Leute dazu zu bringen, Komponententests zu akzeptieren, weil es wie Zeitverschwendung ("Wir könnten ein anderes Geldverdienen-Projekt programmieren!") Oder rekursiv ("Und dann müssen wir Testfälle für die Testfälle schreiben!") Erscheint. Ich bin schuldig, beides gesagt zu haben.

Wenn Sie zum ersten Mal einen Fehler finden, müssen Sie sich der Wahrheit stellen, dass Sie nicht perfekt sind (wie schnell vergessen wir Programmierer!) Und Sie sagen: "Hmmm."


Ein weiterer Aspekt des Unit-Tests ist, dass der Code geschrieben werden muss, um testbar zu sein. Das Erkennen, dass mancher Code leicht testen kann und mancher Code nicht, macht einen guten Programmierer zu "Hmmm".


Haben Sie Ihren Kollegen gefragt, warum Unit-Tests nur für nach außen gerichtete APIs nützlich sind?


Eine Möglichkeit, den Wert von Komponententests zu ermitteln, besteht darin, auf einen unangenehmen Fehler zu warten und dann zu zeigen, wie dies durch Komponententests hätte verhindert werden können. Das heißt nicht, sie ins Gesicht zu reiben, sondern die Einheitentests von einem theoretischen Elfenbeinturm zu einer Realität im Graben zu bewegen.

Eine andere Möglichkeit besteht darin, zu warten, bis derselbe Fehler zweimal auftritt . "Uhhh, Boss, wir haben Code hinzugefügt, um nach dem Problem der letzten Woche auf Null zu testen, aber der Benutzer hat diesmal ein leeres Zeichen eingegeben!"


Mit gutem Beispiel vorangehen. Schreiben Sie Unit-Tests für IHREN Code und zeigen Sie Ihrem Chef den Wert. Dann sehen Sie nach, ob der Chef eines Tages zum Mittagessen Pizza bestellt und eine Präsentation hält.


Schließlich kann ich Ihnen nicht die Erleichterung sagen, die ich verspüre, wenn wir kurz davor sind, zu stoßen, und ich bekomme einen grünen Balken von den Unit-Tests.

Tony Ennis
quelle
2

Es gibt zwei Arten von privatem Code: privaten Code, der von öffentlichem Code aufgerufen wird (oder privaten Code, der von privatem Code aufgerufen wird, der von öffentlichem Code aufgerufen wird (oder ...)), und privaten Code, der schließlich nicht von öffentlichem Code aufgerufen wird Code.

Ersteres wird bereits durch die Tests für den öffentlichen Code getestet. Letzteres kann nicht aufgerufen werden , überhaupt und sollte daher nicht getestet gelöscht werden.

Beachten Sie, dass bei TDD kein ungetesteter privater Code vorhanden sein kann.

Jörg W. Mittag
quelle
In unserem System sind 99% des Codes von der dritten Art : privat, nicht durch öffentlichen Code aufgerufen und für das System wesentlich (nur ein minimaler Teil unseres Systems verfügt über eine externe öffentliche API).
Wizard79
1
Msgstr "Wenn Sie TDD ausführen, ist es unmöglich, dass nicht getesteten privaten Code existiert." <- einen Testfall löschen, ohne zu wissen, dass der Test der einzige Test ist, der einen bestimmten Zweig abdeckt. OK, das ist mehr "derzeit ungetesteter" Code, aber es ist leicht zu sehen, dass ein späteres triviales Refactoring diesen Code ändert ... nur Ihre Testsuite deckt ihn nicht mehr ab.
Frank Shearar
2

Beim Unit-Testen geht es darum, Einheiten Ihres Codes zu testen. Es liegt an Ihnen, zu definieren, was eine Einheit ist. Ihre Mitarbeiter definieren Einheiten als API-Elemente.

Auf jeden Fall sollte das Testen der API auch zur Ausübung von privatem Code führen. Wenn Sie die Codeabdeckung als Indikator für den Fortschritt des Komponententests definieren, testen Sie am Ende Ihren gesamten Code. Wenn ein Teil des Codes nicht erreicht wurde, geben Sie Ihren Mitarbeitern drei Möglichkeiten:

  • Definieren Sie einen weiteren Testfall, um diesen Teil abzudecken.
  • Code analysieren, um zu rechtfertigen, warum er im Rahmen von Unit-Tests nicht abgedeckt werden kann, aber in anderen Situationen abgedeckt werden sollte,
  • Entfernen Sie toten Code, der weder abgedeckt noch gerechtfertigt ist.
mouviciel
quelle
In unserem System ist die API nur ein minimaler Teil, der die Automatisierung / Fernsteuerung für Anwendungen von Drittanbietern ermöglicht. Das Testen nur der API berücksichtigt eine Codeabdeckung von 1% ...
Wizard79