Sinn von Unit-Tests ohne TDD

28

Wir haben ein neues (ziemlich großes) Projekt gestartet, das wir mit TDD entwickeln wollten.

Die Idee von TDD ist gescheitert (viele geschäftliche und nicht geschäftliche Gründe), aber im Moment haben wir ein Gespräch - sollten wir trotzdem Unit-Tests schreiben oder nicht. Mein Freund sagt, dass es keinen (oder fast keinen) Sinn gibt, Unit-Tests ohne TDD zu schreiben. Wir sollten uns nur auf Integrationstests konzentrieren. Ich glaube im Gegenteil, dass es immer noch Sinn macht, einfache Unit-Tests zu schreiben, um den Code zukunftssicherer zu machen. Was denkst du?

Hinzugefügt: Ich denke, dass dies kein Duplikat von >> dieser Frage << ist - ich verstehe den Unterschied zwischen UT und TDD. Bei meiner Frage geht es nicht um Unterschiede , sondern um den Sinn , Unit Tests ohne TDD zu schreiben.

ex3v
quelle
22
Ich bin neugierig, was Ihr Freund für eine absurde Haltung hat ...
Telastyn
11
Ich wette, dass die überwiegende Mehrheit der Projekte mit einigen Komponententests kein TDD verwenden.
Casey
2
Was werden Ihre Integrationsstufen sein? Was werden Ihre Einheiten sein? Wie oft werden Sie unter jedem Testlevel refactoren? Wie schnell werden Ihre Integrationstests ausgeführt? Wie einfach werden sie zu schreiben sein? Wie viele kombinatorische Fälle generieren verschiedene Teile Ihres Codes? etc ... Wenn Sie die Antworten auf diese Fragen nicht kennen, ist es vielleicht zu früh, um feste Entscheidungen zu treffen. Manchmal ist TDD großartig. Manchmal sind die Vorteile weniger klar. Manchmal sind Unit-Tests unerlässlich. Manchmal sind Sie mit einer Reihe anständiger Integrationstests fast genauso viel und viel flexibler ... Lassen Sie Ihre Optionen offen.
topo Reinstate Monica
2
Testen Sie nicht alles, wenn Sie kein TDD durchführen , da dies aus Erfahrung einige praktische Ratschläge sind. Machen Sie eine Entscheidung darüber, welche Arten von Tests wertvoll sind. Ich habe festgestellt, dass Komponententests für reine Eingabe- / Ausgabemethoden außerordentlich wertvoll sind, während Integrationstests auf einer sehr hohen Ebene der Anwendung (z. B. das Senden von Webanforderungen für eine Webanwendung) ebenfalls außerordentlich wertvoll sind. Achten Sie auf Mid-Tier-Integrationstests und Unit-Tests, die eine Menge Mock-Setup erfordern. Sehen Sie sich auch dieses Video an: youtube.com/watch?v=R9FOchgTtLM .
jpmc26
Ihr Update ist in Bezug auf die von Ihnen gestellte Frage nicht sinnvoll. Wenn Sie den Unterschied zwischen TDD und Komponententests verstehen, was hindert Sie daran, Komponententests zu schreiben? Abstimmung, um Ihre Frage geschlossen zu lassen, obwohl ich ein Argument für das Schließen als "unklar über das, was Sie fragen" anstatt als Duplikat sehen könnte.

Antworten:

52

TDD wird hauptsächlich verwendet (1), um die Abdeckung sicherzustellen, (2) und um ein wartbares, verständliches und testbares Design zu erzielen. Wenn Sie TDD nicht verwenden, erhalten Sie keine garantierte Codeabdeckung. Das bedeutet jedoch keinesfalls, dass Sie dieses Ziel aufgeben und mit 0% Deckung munter weiterleben sollten.

Regressionstests wurden aus einem bestimmten Grund erfunden. Der Grund dafür ist, dass sie Ihnen auf lange Sicht mehr Zeit bei der Vermeidung von Fehlern sparen als zusätzlichen Aufwand beim Schreiben. Dies wurde immer wieder bewiesen. Daher werden , wenn Sie ernsthaft davon überzeugt , dass Ihre Organisation ist viel, viel besser auf Software - Engineering als alle Gurus , die Regressionstests empfehlen (oder , wenn Sie auf dem Gehen nach unten sehr bald so planen , dass es ist nicht lange laufen für Sie), ja, Sie sollten unbedingt Komponententests haben, und zwar aus genau dem Grund, der für praktisch jede andere Organisation auf der Welt gilt: weil sie Fehler früher erkennen als Integrationstests, und das spart Geld. Wenn man sie nicht schreibt, ist das so, als würde man freies Geld auf der Straße liegen lassen.

Kilian Foth
quelle
12
"Wenn Sie TDD nicht verwenden, erhalten Sie keine garantierte Codeabdeckung.": Ich denke nicht. Sie können sich zwei Tage lang weiterentwickeln und für die nächsten zwei Tage schreiben Sie die Tests. Der wichtige Punkt ist, dass Sie eine Funktion erst als abgeschlossen betrachten, wenn Sie die gewünschte Codeabdeckung erreicht haben.
Giorgio
5
@DougM - In einer idealen Welt vielleicht ...
Telastyn
7
Leider geht TDD mit Spott einher und bis die Leute damit aufhören, beweist es nur, dass Ihr Test schneller läuft . TDD ist tot. Es lebe das Testen.
MickyD
17
TDD garantiert keine Codeabdeckung. Das ist eine gefährliche Annahme. Sie können gegen Tests codieren, diese Tests bestehen, aber noch Randfälle haben.
Robert Harvey
4
@ MickyDuncan Ich bin nicht ganz sicher, ob ich Ihre Besorgnis vollständig verstehe. Das Verspotten ist eine absolut gültige Technik, mit der eine Komponente von der anderen isoliert wird, damit das Verhalten dieser Komponente unabhängig getestet werden kann. Ja, extrem gesehen kann dies zu überentwickelter Software führen, aber auch zu Entwicklungstechniken, wenn sie nicht richtig eingesetzt werden. Außerdem ist, wie DHH in dem von Ihnen zitierten Artikel feststellt, die Idee, immer nur vollständige Systemtests zu verwenden, genauso schlecht, wenn nicht sogar noch schlimmer. Es ist wichtig, mit Urteilsvermögen zu entscheiden, wie ein bestimmtes Merkmal am besten getestet werden kann .
Jules
21

Ich habe eine relevante Anekdote von etwas, das gerade für mich los ist . Ich arbeite an einem Projekt, das TDD nicht verwendet. Unsere QA-Leute bewegen uns in diese Richtung, aber wir sind ein kleines Outfit und es war ein langer, langwieriger Prozess.

Wie auch immer , ich habe kürzlich eine Drittanbieter-Bibliothek verwendet, um eine bestimmte Aufgabe zu erledigen. Es gab ein Problem bezüglich der Verwendung dieser Bibliothek, daher wurde es mir übertragen, im Wesentlichen eine eigene Version derselben Bibliothek zu schreiben. Insgesamt waren es ungefähr 5.000 Zeilen ausführbaren Code und ungefähr 2 Monate meiner Zeit. Ich weiß, dass Codezeilen eine schlechte Metrik sind, aber für diese Antwort halte ich sie für einen anständigen Größenindikator.

Es gab eine bestimmte Datenstruktur, die es mir ermöglichte, eine beliebige Anzahl von Bits zu verfolgen. Da das Projekt in Java ist, habe ich Java gewählt BitSetund ein wenig modifiziert (ich brauchte die Fähigkeit, auch die führenden 0s zu verfolgen , was Java aus irgendeinem Grund nicht macht ...). Nachdem ich eine Abdeckung von ~ 93% erreicht hatte, begann ich, einige Tests zu schreiben, die das von mir geschriebene System tatsächlich belasten würden. Ich musste bestimmte Aspekte der Funktionalität vergleichen, um sicherzustellen, dass sie schnell genug für meine Endanforderungen sind. Es überrascht nicht, dass eine der Funktionen, die ich von der BitSetSchnittstelle aus überschrieben hatte , bei großen Bitmengen (in diesem Fall Hunderte von Millionen von Bits) absurd langsam war. Andere überschriebene Funktionen beruhten auf dieser einen Funktion, sodass es sich um einen riesigen Flaschenhals handelte.

Was ich am Ende tun mit dem Zeichenbrett würde und einen Weg herauszufinden , die zugrunde liegende Struktur zu manipulieren BitSet, das eine ist long[]. Ich entwarf den Algorithmus, fragte Kollegen nach ihren Eingaben und machte mich dann daran, den Code zu schreiben. Dann habe ich die Unit-Tests durchgeführt. Einige von ihnen sind kaputt gegangen und diejenigen, die mich genau dorthin geführt haben, wo ich in meinem Algorithmus nachsehen musste, um das Problem zu beheben. Nachdem ich alle Fehler aus den Unit-Tests behoben hatte, konnte ich sagen, dass die Funktion so funktioniert, wie sie sollte. Zumindest könnte ich so sicher sein, dass dieser neue Algorithmus genauso gut funktioniert wie der vorherige.

Dies ist natürlich nicht kugelsicher. Wenn mein Code einen Fehler enthält, auf den die Komponententests nicht prüfen, weiß ich es nicht. Aber natürlich hätte genau dieser Fehler auch in meinem langsameren Algorithmus vorkommen können. Ich kann jedoch mit größter Sicherheit sagen, dass ich mir keine Sorgen über die falsche Ausgabe dieser bestimmten Funktion machen muss. Bereits vorhandene Komponententests ersparten mir Stunden, vielleicht Tage, um den neuen Algorithmus auf seine Richtigkeit zu testen.

Das ist der Punkt, an dem Unit-Tests unabhängig von TDD durchgeführt werden müssen - das heißt, Unit-Tests erledigen dies für Sie in TDD und auch außerhalb von TDD, wenn Sie am Ende den Code umgestalten / pflegen. Dies sollte natürlich mit regelmäßigen Regressionstests, Rauchtests, Fuzzy-Tests usw. kombiniert werden, aber Unit- Tests testen, wie der Name schon sagt, die Dinge auf der kleinstmöglichen atomaren Ebene, die Ihnen Aufschluss darüber gibt, wo Fehler aufgetreten sind.

In meinem Fall müsste ich ohne die vorhandenen Komponententests irgendwie eine Methode finden, um sicherzustellen, dass der Algorithmus die ganze Zeit funktioniert. Was am Ende ... sehr nach Unit Testing klingt, nicht wahr?

Shaz
quelle
7

Sie können den Code grob in 4 Kategorien unterteilen:

  1. Einfach und ändert sich selten.
  2. Einfach und häufig wechselnd.
  3. Komplex und ändert sich selten.
  4. Komplex und häufig wechselnd.

Unit-Tests werden umso wertvoller (sie werden wahrscheinlich wichtige Fehler finden), je weiter unten Sie sich befinden. In meinen persönlichen Projekten mache ich fast immer TDD in Kategorie 4. In Kategorie 3 mache ich normalerweise TDD, es sei denn, das manuelle Testen ist einfacher und schneller. Das Schreiben von Antialiasing-Code wäre beispielsweise komplex, aber viel einfacher visuell zu überprüfen als das Schreiben eines Komponententests. Der Komponententest lohnt sich daher für mich nur, wenn sich dieser Code häufig ändert. Den Rest meines Codes habe ich erst getestet, nachdem ich einen Fehler in dieser Funktion gefunden habe.

Es ist manchmal schwierig, im Voraus zu wissen, in welche Kategorie ein bestimmter Codeblock passt. Der Wert von TDD ist, dass Sie keinen der komplexen Komponententests versehentlich verpassen. Die Kosten für TDD belaufen sich auf die Zeit, die Sie mit dem Schreiben der einfachen Komponententests verbringen. Normalerweise wissen Menschen, die Erfahrung mit einem Projekt haben, jedoch mit einem angemessenen Maß an Sicherheit, in welche Kategorie verschiedene Teile des Codes passen. Wenn Sie kein TDD machen, sollten Sie zumindest versuchen, die wertvollsten Tests zu schreiben.

Karl Bielefeldt
quelle
1
Wenn Sie an Code arbeiten, den Sie mit Ihrem Antialiasing-Beispiel vorschlagen, ist es das Beste, den Code experimentell zu entwickeln und dann einige Charakterisierungstests hinzuzufügen , um sicherzustellen, dass ich den Algorithmus später nicht versehentlich unterbreche. Charakterisierungstests sind sehr schnell und einfach zu entwickeln, so dass der Aufwand hierfür sehr gering ist.
Jules
1

Unabhängig davon, ob es sich um Einheits-, Komponenten-, Integrations- oder Abnahmetests handelt, ist es wichtig, dass diese automatisiert werden müssen. Das Fehlen automatisierter Tests ist ein schwerwiegender Fehler für jede Art von Software, von einfachen CRUDs bis hin zu komplexesten Berechnungen. Der Grund dafür ist, dass das Schreiben der automatisierten Tests immer weniger kostet als die kontinuierliche manuelle Ausführung aller Tests, wenn Sie dies nicht tun, und zwar um Größenordnungen. Nachdem Sie sie geschrieben haben, müssen Sie nur einen Knopf drücken, um zu sehen, ob sie bestanden haben oder nicht. Manuelle Tests dauern immer lange und hängen von Menschen ab (Lebewesen, denen es langweilig wird, denen es möglicherweise an Aufmerksamkeit mangelt usw.), um zu überprüfen, ob die Tests bestanden wurden oder nicht. Kurz gesagt, schreiben Sie immer automatisierte Tests.

Nun zu dem Grund, warum Ihr Kollege möglicherweise gegen die Durchführung von Unit-Tests ohne TDD ist: Es ist wahrscheinlich, weil es schwieriger ist, Tests zu vertrauen, die nach dem Produktionscode geschrieben wurden. Und wenn Sie Ihren automatisierten Tests nicht vertrauen können, sind sie nichts wert . Nach dem TDD-Zyklus müssen Sie zuerst einen Test fehlschlagen lassen (aus dem richtigen Grund), damit Sie den Produktionscode schreiben können, damit er erfolgreich ist (und nicht mehr). Dieser Prozess testet im Wesentlichen Ihre Tests, sodass Sie ihnen vertrauen können. Ganz zu schweigen von der Tatsache, dass Sie durch das Schreiben von Tests vor dem eigentlichen Code gezwungen sind, Ihre Einheiten und Komponenten so zu gestalten, dass sie leichter testbar sind (hohes Maß an Entkopplung, angewendetes SRP usw.). Obwohl TDD natürlich Disziplin erfordert .

Wenn Sie stattdessen den gesamten Produktionscode zuerst schreiben, wenn Sie die Tests dafür schreiben, erwarten Sie, dass sie beim ersten Ausführen bestanden werden. Dies ist sehr problematisch, da Sie möglicherweise einen Test erstellt haben, der 100% Ihres Produktionscodes abdeckt, ohne das richtige Verhalten zu bestätigen (möglicherweise werden nicht einmal Behauptungen aufgestellt! Ich habe gesehen, dass dies passiert ist ), da Sie keinen Fehler feststellen können Überprüfen Sie zuerst, ob es aus dem richtigen Grund fehlschlägt. Sie haben also möglicherweise falsch-positive Ergebnisse. False-Positives brechen letztendlich das Vertrauen in Ihre Testsuite und zwingen die Benutzer dazu, erneut auf manuelle Tests zurückzugreifen. Sie haben also die Kosten für beide Prozesse (Schreiben von Tests + manuelle Tests).

Dies bedeutet, dass Sie einen anderen Weg finden müssen , um Ihre Tests zu testen , wie dies bei TDD der Fall ist. Sie greifen also auf das Debuggen, Kommentieren von Teilen des Produktionscodes usw. zurück, um den Tests vertrauen zu können. Das Problem ist, dass der Prozess des "Testens Ihrer Tests" auf diese Weise viel langsamer ist. Wenn Sie diese Zeit zu der Zeit addieren, die Sie mit der manuellen Ausführung von Ad-hoc-Tests verbringen (da Sie keine automatischen Tests haben, während Sie den Produktionscode codieren), ergibt sich nach meiner Erfahrung ein Gesamtprozess, der viel langsamer ist als das Üben TDD "nach dem Vorbild" (Kent Beck - TDD nach Vorbild). Außerdem bin ich bereit, hier eine Wette abzuschließen und zu sagen, dass es viel disziplinierter ist, Ihre Tests zu testen, nachdem sie geschrieben wurden als TDD.

Vielleicht kann Ihr Team die "geschäftlichen und nicht geschäftlichen Gründe" für die Nichteinhaltung von TDD überdenken. Nach meiner Erfahrung denken die Leute, TDD sei langsamer als das Schreiben von Unit-Tests, nachdem der Code fertig ist. Diese Annahme ist fehlerhaft, wie Sie oben gelesen haben.

MichelHenrich
quelle
0

Oft hängt die Qualität der Tests von ihrer Herkunft ab. Ich habe regelmäßig die Schuld, dass ich kein "echtes" TDD mache. Ich schreibe einen Code, um zu beweisen, dass die Strategie, die ich verwenden möchte, tatsächlich funktioniert, und behandle dann jeden Fall, den dieser Code später mit Tests unterstützen soll. In der Regel handelt es sich bei der Codeeinheit um eine Klasse, um Ihnen eine allgemeine Vorstellung davon zu geben, wie viel Arbeit ich ohne Testabdeckung gerne erledigen werde - das heißt, keine große Menge. Dies bedeutet, dass die semantische Bedeutung der Tests gut mit dem zu testenden System im "fertigen" Zustand übereinstimmt - weil ich ihnen geschrieben habe, inwieweit das SUT Fälle erfüllt und wie es sie erfüllt.

Umgekehrt tendiert TDD mit seiner Politik des aggressiven Refactorings dazu, Tests zu veralten, mindestens so schnell, wie Sie sie schreiben können, als die öffentliche Schnittstelle des Systems, das sich im Test befindet. Ich persönlich finde die mentale Arbeitsbelastung sowohl die funktionellen Einheiten der Entwicklung neuer Anwendungen und die Semantik der Tests zu halten , dass sie synchron decken zu hoch zu sein , um meine Konzentration und die Test Wartung häufig rutscht zu halten. Die Codebasis endet mit herumhängenden Tests, die nichts von Wert testen oder einfach falsch sind. Wenn Sie die Disziplin und die mentale Kapazität haben, die Testsuite auf jeden Fall auf dem neuesten Stand zu halten, üben Sie TDD so rigoros, wie Sie möchten. Ich nicht, deshalb fand ich es weniger erfolgreich.

Tom W
quelle
0

Tatsächlich erwähnte Onkel Bob einen sehr interessanten Punkt in einem seiner Clean Coders-Videos. Er sagte, dass der Rot-Grün-Refaktor-Zyklus auf zwei Arten angewendet werden kann.

1. ist der herkömmliche TDD-Weg. Schreiben Sie einen nicht bestandenen Test, bestehen Sie den Test und überarbeiten Sie ihn schließlich.

Der zweite Weg ist, ein sehr kleines Stück Produktionscode zu schreiben und dem durch seinen Unit-Test und anschließenden Refactor sofort zu folgen.

Die Idee ist, in sehr kleinen Schritten voranzukommen. Natürlich verlieren Sie die Bestätigung des Produktionscodes, dass Ihr Test von rot auf grün übergegangen ist. In einigen Fällen habe ich jedoch hauptsächlich mit Junior-Entwicklern gearbeitet, die sich geweigert haben, TDD überhaupt zu verstehen.

Ich wiederhole noch einmal (und dies wurde von Onkel Bob betont), dass die Idee darin besteht, in sehr kleinen Schritten den gerade hinzugefügten Produktionscode sofort zu testen.

Songo
quelle
"... die Idee ist, in sehr kleinen Schritten den gerade hinzugefügten Produktionscode sofort zu testen.": Dem stimme ich nicht zu. Was Sie beschreiben, ist gut, wenn Sie bereits eine klare Vorstellung davon haben, was Sie tun möchten, und an den Details arbeiten möchten, aber zuerst den Überblick behalten müssen. Andernfalls können Sie sich in sehr kleinen Schritten (Testen, Entwickeln, Testen, Entwickeln) in den Details verlieren. "Wenn du nicht weißt, wohin du gehst, kommst du vielleicht nicht dorthin."
Giorgio