Wenn Ihr Unit-Test-Code "riecht", spielt es dann wirklich eine Rolle?

52

Normalerweise werfe ich meine Unit-Tests einfach mit Kopieren und Einfügen und allen anderen schlechten Praktiken zusammen. Die Unit-Tests sehen normalerweise ziemlich hässlich aus, sie stecken voller "Code-Gerüche", aber ist das wirklich wichtig? Ich sage mir immer, solange der "echte" Code "gut" ist, ist alles, was zählt. Darüber hinaus erfordert Unit-Testing normalerweise verschiedene "stinkende Hacks" wie Stubbing-Funktionen.

Wie besorgt sollte ich über schlecht gestaltete ("stinkende") Unit-Tests sein?

Buttons840
quelle
8
Wie schwer kann es sein, den Code zu reparieren, den Sie immer kopieren?
JeffO
7
Code-Geruch in den Tests könnte leicht ein Hinweis auf verborgenen Geruch im Rest des Codes sein - warum sollten Sie sich den Tests nähern, als ob sie kein "echter" Code wären?
HorusKol

Antworten:

75

Sind Gerüche von Unit-Tests wichtig? Ja definitiv. Sie unterscheiden sich jedoch von Code-Gerüchen, da Komponententests einem anderen Zweck dienen und unterschiedliche Spannungen aufweisen, die für ihr Design maßgeblich sind. Viele Gerüche im Code gelten nicht für Tests. In Anbetracht meiner TDD-Mentalität würde ich tatsächlich argumentieren, dass Gerüche von Komponententests wichtiger sind als Code-Gerüche, da der Code nur dazu dient, die Tests zu erfüllen.

Hier sind einige übliche Gerüche für Unit-Tests:

  • Fragilität : Scheitern Ihre Tests häufig und unerwartet, selbst bei scheinbar trivialen oder nicht verwandten Codeänderungen?
  • State Leak : Schlagen Ihre Tests unterschiedlich fehl, zum Beispiel in welcher Reihenfolge sie ausgeführt werden?
  • Setup / Teardown Bloat : Sind Ihre Setup / Teardown-Blöcke lang und wachsen sie länger? Führen sie irgendeine Art von Geschäftslogik durch?
  • Langsame Laufzeit : Nehmen Ihre Tests viel Zeit in Anspruch? Dauert einer Ihrer einzelnen Komponententests länger als eine Zehntelsekunde? (Ja, ich meine es ernst, eine Zehntelsekunde.)
  • Reibung : Erschweren bestehende Tests das Schreiben neuer Tests? Haben Sie beim Refactoring häufig mit Testfehlern zu kämpfen?

Die Bedeutung von Gerüchen besteht darin, dass sie nützliche Indikatoren für Design oder andere grundlegendere Themen sind, z. B. "Wo Rauch ist, ist Feuer". Suchen Sie nicht nur nach Testgerüchen, sondern auch nach deren Ursache.

Im Folgenden sind einige bewährte Methoden für Komponententests aufgeführt:

  • Schnelles, zielgerichtetes Feedback : Ihre Tests sollten den Fehler schnell eingrenzen und nützliche Informationen zur Ursache liefern.
  • Abstand zwischen Testcode minimieren : Zwischen dem Test und dem Code, der ihn implementiert, sollte ein klarer und kurzer Weg bestehen. Lange Entfernungen erzeugen unnötig lange Rückkopplungsschleifen.
  • Testen Sie eine Sache nach der anderen : Unit-Tests sollten nur eine Sache testen. Wenn Sie etwas anderes testen müssen, schreiben Sie einen weiteren Test.
  • Ein Fehler ist ein Test, den Sie vergessen haben zu schreiben : Was können Sie aus diesem Fehler lernen, um in Zukunft bessere, vollständigere Tests zu schreiben?
Rein Henrichs
quelle
2
"Unit-Tests dienen einem anderen Zweck und haben unterschiedliche Spannungsverhältnisse, die ihren Entwurf beeinflussen." Zum Beispiel sollten sie DÄMPFEN, nicht unbedingt TROCKEN.
Jörg W Mittag
3
@ Jörg Einverstanden, aber steht DAMP eigentlich für irgendetwas? : D
Rein Henrichs
5
@Rein: Beschreibende und aussagekräftige Sätze. Auch nicht ganz trocken. Siehe codeshelter.wordpress.com/2011/04/07/…
Robert Harvey
+1. Ich wusste auch nicht, was DAMP bedeutet.
egarcia
2
@ironcode Wenn Sie nicht isoliert an einem System arbeiten können, ohne befürchten zu müssen, die Integration mit anderen Systemen zu unterbrechen, riecht das für mich nach einer engen Kopplung. Dies ist genau der Zweck, um Testgerüche wie eine lange Laufzeit zu identifizieren: Sie informieren Sie über Konstruktionsprobleme wie eine enge Kopplung. Die Antwort sollte nicht "Oh, dann ist dieser Testgeruch ungültig" sein, sondern "Was sagt mir dieser Geruch über mein Design?" Unit-Tests, die die externe Schnittstelle Ihres Systems spezifizieren, sollten ausreichen, um festzustellen, ob Ihre Änderungen die Integration mit den Kunden beeinträchtigen oder nicht.
Rein Henrichs
67

Betroffen sein. Sie schreiben Komponententests, um zu beweisen, dass Ihr Code so funktioniert, wie Sie es erwarten. Sie ermöglichen es Ihnen, schnell und sicher umzugestalten. Wenn Ihre Tests zerbrechlich, schwer zu verstehen oder schwer zu warten sind, ignorieren Sie fehlgeschlagene Tests oder deaktivieren sie, wenn sich Ihre Codebasis weiterentwickelt. Dadurch werden viele Vorteile des Schreibens von Tests zunichte gemacht.

Jayraynet
quelle
17
+1 In dem Moment, in dem Sie anfangen, nicht bestandene Tests zu ignorieren, bringen sie keinen Mehrwert.
19

Ich habe gerade The Art of Unit Testing vor ein paar Tagen gelesen . Der Autor befürwortet, dass Sie Ihre Komponententests genauso sorgfältig durchführen wie Ihren Produktionscode.

Ich habe schlecht geschriebene, nicht zu wartende Tests aus erster Hand erlebt. Ich habe einige von meinen eigenen geschrieben. Es ist so gut wie garantiert, dass ein Test, bei dem man sich Sorgen machen muss, nicht aufrechterhalten wird. Sobald die Tests nicht mehr mit dem getesteten Code synchron sind, sind sie zu Nestern von Lügen und Betrug geworden. Der springende Punkt bei Unit-Tests ist es, Vertrauen zu schaffen, dass wir nichts kaputt gemacht haben (das heißt, sie schaffen Vertrauen). Wenn den Tests nicht vertraut werden kann, sind sie schlimmer als nutzlos.

Joshua Smith
quelle
4
+1 Sie müssen Tests beibehalten, genau wie der Produktionscode
Hamish Smith
Ein weiteres sehr gutes Buch zu diesem Thema ist Gerard Meszaros '"xUnit-Testmuster - Refatcoring-Testcode".
Adrianboimvaser
7

Solange Ihr Unit-Test tatsächlich Ihren Code in "den meisten" Fällen testet. (Meist absichtlich gesagt, da es manchmal schwierig ist, alle möglichen Ergebnisse zu finden). Ich denke, "stinkender" Code ist Ihre persönliche Präferenz. Ich schreibe Code immer so, dass ich ihn innerhalb weniger Sekunden lesen und verstehen kann, anstatt mich durch den Müll zu wühlen und zu versuchen, zu verstehen, was was ist. Vor allem, wenn Sie nach einiger Zeit darauf zurückkommen.

Fazit - seine Prüfung, es wird angenommen, einfach zu sein. Sie möchten sich nicht mit "stinkendem" Code verwechseln.

Luke
quelle
5
+1: Mach dir keine Sorgen um den Unit-Test-Code. Einige der dümmsten Fragen drehen sich darum, den Unit-Test-Code "robuster" zu machen, damit eine Programmänderung den Unit-Test nicht unterbricht. Albernheit. Die Unit-Tests sind spröde ausgelegt. Es ist in Ordnung, wenn sie weniger robust sind als der Anwendungscode, den sie testen sollen.
S.Lott
1
@ S.lott Beide sind einverstanden und nicht einverstanden, aus Gründen, die in meiner Antwort besser erklärt werden.
Rein Henrichs
1
@Rein Henrichs: das sind weitaus ernstere Gerüche als in der Frage beschrieben. "Kopieren und Einfügen" und "ziemlich hässlich aussehen" klingen nicht so schlecht wie die Gerüche, die Sie beschreiben, wenn die Tests unzuverlässig sind.
S.Lott
1
@ S.Lott genau, ich denke, wenn wir über "Unit-Testing-Gerüche" sprechen wollen, ist es entscheidend, diese Unterscheidung zu treffen. Code-Gerüche in Unit-Tests sind es oft nicht. Sie sind für verschiedene Zwecke geschrieben und die Spannungen, die sie in einem Kontext stinken lassen, sind in dem anderen sehr unterschiedlich.
Rein Henrichs
2
@ S.Lott Übrigens scheint dies eine perfekte Gelegenheit zu sein, die viel vernachlässigte Chat-Funktion zu nutzen :)
Rein Henrichs
6

Bestimmt. Einige Leute sagen, "jeder Test ist besser als gar kein Test". Ich stimme dem überhaupt nicht zu - schlecht geschriebene Tests belasten Ihre Entwicklungszeit und Sie verschwenden am Ende Tage damit, "kaputte" Tests zu reparieren, da es sich in erster Linie nicht um gute Komponententests handelte. Für mich sind im Moment die beiden Dinge, auf die ich mich konzentriere, um meine Tests wertvoller zu machen, als eine Belastung:

Wartbarkeit

Sie sollten das Ergebnis testen ( was passiert), nicht die Methode ( wie passiert es). Ihr Setup für den Test sollte so weit wie möglich von der Implementierung entkoppelt sein: Richten Sie nur Ergebnisse für Serviceabrufe usw. ein, die unbedingt erforderlich sind.

  • Verwenden Sie ein spöttisches Framework, um sicherzustellen, dass Ihre Tests nicht von externen Faktoren abhängig sind
  • Bevorzugen Sie Stubs gegenüber Mocks (wenn Ihr Framework sie unterscheidet), wo immer dies möglich ist
  • Keine Logik in Tests! Ifs, Switches, For-Eaches, Cases, Try-Catches usw. sind allesamt große Nein-Nummern, da sie Fehler in den Testcode selbst einführen können

Lesbarkeit

Es ist in Ordnung, ein bisschen mehr Wiederholungen in Ihren Tests zuzulassen, die Sie normalerweise in Ihrem Produktionscode nicht zulassen würden, wenn dies die Lesbarkeit erhöht. Gleichen Sie dies einfach mit den oben genannten Wartungsmöglichkeiten aus. Seien Sie ausdrücklich dabei, was der Test tut!

  • Versuchen Sie, für Ihre Tests einen "Arrangier-, Handlungs- und Durchsetzungsstil" beizubehalten. Dies trennt Ihre Einstellungen und Erwartungen an das Szenario von der ausgeführten Aktion und dem durchsetzten Ergebnis.
  • Behalten Sie eine logische Zusicherung pro Test bei (wenn der Name Ihres Tests "und" enthält, müssen Sie ihn möglicherweise in mehrere Tests aufteilen).

Abschließend sollten Sie sich sehr mit "stinkenden" Tests beschäftigen - sie können nur eine Verschwendung Ihrer Zeit sein und keinen Wert liefern.

Du hast gesagt:

Unit-Tests erfordern normalerweise verschiedene "stinkende Hacks" wie Stubbing-Funktionen.

Klingt so, als ob Sie es definitiv gebrauchen könnten, sich über einige Unit-Testing-Techniken zu informieren, beispielsweise über die Verwendung eines Mocking-Frameworks, um Ihr Leben viel einfacher zu machen. Ich würde The Art of Unit Testing , das das oben Genannte und vieles mehr behandelt, wärmstens empfehlen . Ich fand es aufschlussreich, nachdem ich lange Zeit mit schlecht geschriebenen, nicht zu wartenden, "stinkenden" Tests zu kämpfen hatte. Es ist eine der besten Zeitinvestitionen, die ich in diesem Jahr getätigt habe!

mjhilton
quelle
Hervorragende Antwort. Ich habe nur ein kleines Problem: Viel wichtiger als "eine logische Aussage pro Test" ist eine Aktion pro Test. Es spielt keine Rolle, wie viele Schritte es dauert, um einen Test zu arrangieren, es sollte nur eine "zu testende Produktionscode-Aktion" geben. Wenn diese Aktion mehr als eine Nebenwirkung haben sollte, können Sie durchaus mehrere Behauptungen aufstellen.
Enttäuscht
5

Zwei Fragen an Sie:

  • Sind Sie sich absolut sicher, dass Sie testen, was Sie zu testen glauben ?
  • Wenn sich jemand anders den Komponententest ansieht, kann er dann herausfinden, was der Code tun soll ?

Es gibt Möglichkeiten, sich wiederholende Aufgaben in Ihren Komponententests zu erledigen, die am häufigsten als Setup- und Teardown-Code verwendet werden. Grundsätzlich gibt es eine Testaufbau- und eine Testabbaumethode - alle Unit-Test-Frameworks unterstützen dies.

Unit-Tests sollten klein und leicht verständlich sein. Wenn dies nicht der Fall ist und der Test fehlschlägt, wie können Sie das Problem in relativ kurzer Zeit beheben? Machen Sie es sich ein paar Monate später einfach, wenn Sie zum Code zurückkehren müssen.

Berin Loritsch
quelle
5

Wenn der Müll anfängt zu riechen, ist es Zeit ihn herauszunehmen. Ihr Testcode sollte so sauber sein wie Ihr Produktionscode. Würdest du es deiner Mutter zeigen?

Bill Leeper
quelle
3

Neben den anderen Antworten hier.

Ein schlechter Qualitätscode in Komponententests ist nicht auf Ihre Komponententestsuite beschränkt.

Eine der Rollen, die Unit Testing übernimmt, ist Documentation.

Die Unit-Test-Suite ist einer der Orte, an denen herausgefunden werden soll, wie eine API verwendet werden soll.

Es ist nicht unwahrscheinlich, dass Aufrufer Ihrer API Teile Ihrer Unit-Testsuite kopieren, was dazu führt, dass Ihr fehlerhafter Testsuite-Code den Live-Code an anderer Stelle infiziert.

Paul Butcher
quelle
3

Ich habe meine Antwort fast nicht eingereicht, da ich eine Weile gebraucht habe, um herauszufinden, wie ich das überhaupt als legitime Frage betrachten kann.

Die Gründe, warum sich Programmierer mit "Best Practices" beschäftigen, liegen nicht in ästhetischen Gründen oder weil sie "perfekten" Code wollen. Es ist, weil es ihnen Zeit spart.

  • Das spart Zeit, da guter Code leichter zu lesen und zu verstehen ist.
  • Dies spart Zeit, da das Auffinden eines Fehlers einfacher und schneller ist.
  • Das spart Zeit, denn wenn Sie den Code erweitern möchten, ist dies einfacher und schneller.

Die Frage, die Sie (aus meiner Sicht) stellen, lautet also: Soll ich mir Zeit sparen oder Code schreiben, der meine Zeit verschwendet?

Zu dieser Frage kann ich nur sagen, wie wichtig ist deine Zeit?

Zu Ihrer Information: Stubbing, Mocking und Monkey Patching haben alle legitime Verwendungszwecke. Sie "riechen" nur, wenn ihre Verwendung nicht angemessen ist.

dietbuddha
quelle
2

Ich versuche ausdrücklich, meine Unit-Tests NICHT zu robust zu machen. Ich habe Unit-Tests gesehen, die anfangen, Fehler zu behandeln, weil sie robust sind. Was Sie am Ende haben, sind Tests, die die Fehler verschlucken, die sie zu fangen versuchen. Unit-Tests müssen auch ziemlich oft einige funky Dinge tun, damit die Dinge funktionieren. Denken Sie über die gesamte Idee nach, dass private Accessors Reflektionen verwenden. Wenn ich eine Menge davon im Produktionscode sehen würde, würde ich mir 9 von 10 Sorgen machen. Ich denke, es sollte mehr Zeit darauf verwendet werden, darüber nachzudenken, was getestet wird , anstatt Code Sauberkeit. Die Tests müssen ohnehin ziemlich häufig geändert werden, wenn Sie größere Umgestaltungen vornehmen. Warum also nicht einfach zusammen hacken, ein wenig weniger Eigentümergefühle haben und motivierter sein, sie zu gegebener Zeit umzuschreiben oder zu überarbeiten?

Morgan Herlocker
quelle
2

Wenn Sie sich für eine starke, niedrige Abdeckung entscheiden, müssen Sie mindestens so viel Zeit mit dem Testcode wie mit dem Produktcode verbringen, wenn Sie ernsthafte Änderungen vornehmen.

Abhängig von der Komplexität des Testaufbaus kann der Code komplexer sein. (httpcontext.current vs. das schreckliche Monster, das versucht, ein falsches genau zu bauen, zum Beispiel)

Sofern Ihr Produkt nicht etwas ist, bei dem Sie selten Änderungen an vorhandenen Schnittstellen vornehmen und Ihre Eingaben auf Geräteebene nicht sehr einfach einzurichten sind, wäre ich mindestens genauso besorgt über das Verständnis der Tests wie das eigentliche Produkt.

Rechnung
quelle
0

Code-Gerüche sind nur ein Problem im Code, das zu einem späteren Zeitpunkt geändert oder verstanden werden muss.

Ich denke, Sie sollten Code-Gerüche korrigieren, wenn Sie sich dem gegebenen Komponententest "nähern" müssen, aber nicht vorher.

Ian
quelle