Testgetriebene Entwicklung

15

Ich habe mehr als zwei Jahre Erfahrung in der Anwendungsentwicklung. In diesen zwei Jahren war meine Herangehensweise an die Entwicklung wie folgt

  1. Anforderungen analysieren
  2. Identität Kernkomponente / Objekte, Erforderliche Funktionen, Verhalten, Prozess und deren Einschränkungen
  3. Erstellen Sie Klassen, Beziehungen zwischen ihnen, Einschränkungen für das Verhalten und den Status von Objekten
  4. Erstellen Sie Funktionen, verarbeiten Sie mit Verhaltensbeschränkungen gemäß den Anforderungen
  5. Anwendung manuell testen
  6. Wenn sich die Anforderungen ändern, ändern Sie die Komponenten / Funktionen und testen Sie die Anwendung manuell

Kürzlich habe ich TDD kennengelernt und bin der Meinung, dass dies eine sehr gute Möglichkeit für die Entwicklung ist, da entwickelter Code gute Gründe hat und viele Probleme nach der Bereitstellung behoben werden.

Aber mein Problem ist, dass ich nicht in der Lage bin, zuerst Tests zu erstellen. Ich identifiziere vielmehr Komponenten und schreibe nur Tests für sie, bevor ich tatsächlich Komponenten schreibe. meine Frage ist

  1. Mache ich es richtig? Wenn nicht was genau muss ich ändern
  2. Können Sie feststellen, ob der von Ihnen geschriebene Test ausreicht?
  3. Ist es empfehlenswert, Tests für sehr einfache Funktionen zu schreiben, die 1 + 1 = 2 entsprechen könnten, oder handelt es sich nur um ein Übermaß?
  4. Ist es gut, die Funktionalität zu ändern und entsprechend zu testen, ob sich die Anforderung ändert?
Yogesh
quelle
2
"Ich identifiziere Komponenten und schreibe nur einen Test für sie, bevor ich tatsächlich Komponenten schreibe.": Ich finde das richtig: Sie identifizieren zuerst die grobe Architektur Ihres Systems und beginnen dann mit der Codierung. Während des Codierens (TDD) erarbeiten Sie die Details der einzelnen Komponenten und stellen möglicherweise Probleme mit Ihrer Architektur fest, die Sie auf diesem Weg beheben können. Aber ich finde es in Ordnung, dass Sie nicht ohne vorherige Analyse mit dem Codieren beginnen.
Giorgio
Sie könnten auch automatisierte Unit- / Integrationstests durchführen, ohne TDD durchzuführen. Die beiden sind oft verwechselt, aber sie sind nicht dasselbe.
Andres F.

Antworten:

19

Mache ich es richtig? Wenn nicht was genau muss ich ändern

Es ist schwer zu sagen, nur aus dieser kurzen Beschreibung, aber ich vermute, dass Sie es nicht richtig machen. Hinweis: Ich sage nicht, dass das, was Sie tun, nicht funktioniert oder in irgendeiner Weise schlecht ist, aber Sie tun kein TDD. Das mittlere "D" bedeutet "Driven", die Tests steuern alles, den Entwicklungsprozess, den Code, das Design, die Architektur, alles .

In den Tests erfahren Sie, was Sie schreiben müssen, wann Sie es schreiben müssen, was Sie als Nächstes schreiben müssen und wann Sie mit dem Schreiben aufhören müssen. Sie erzählen Ihnen das Design und die Architektur. (Design und Architektur ergeben sich aus dem Code durch Refactoring.) Bei TDD geht es nicht um Tests. Es geht nicht einmal darum, zuerst Tests zu schreiben: Bei TDD geht es darum, dass die Tests Sie fahren lassen. Das Schreiben zuerst ist nur eine notwendige Voraussetzung dafür.

Es spielt keine Rolle, ob Sie den Code tatsächlich aufschreiben oder vollständig ausarbeiten: Sie schreiben (Skelette von) Code in Ihren Kopf und schreiben dann Tests für diesen Code. Das ist kein TDD.

Diese Angewohnheit loszulassen ist schwer . Wirklich sehr, sehr schwer. Für erfahrene Programmierer scheint es besonders schwierig zu sein.

Keith Braithwaite hat eine Übung erstellt, die er TDD nennt, als ob Sie es beabsichtigen . Es besteht aus einer Reihe von Regeln (basierend auf den drei TDD-Regeln von Onkel Bob Martin , aber viel strenger), die Sie genau befolgen müssen und die Sie dazu bringen sollen, TDD strenger anzuwenden. Es funktioniert am besten mit der Paarprogrammierung (damit Ihr Paar sicherstellen kann, dass Sie nicht gegen die Regeln verstoßen) und einem Instruktor.

Die Regeln sind:

  1. Schreiben Sie genau einen neuen Test, den kleinsten Test, der in die Richtung einer Lösung weist
  2. Sieh es scheitern; Kompilierungsfehler gelten als Fehler
  3. Führen Sie den Test aus (1) durch, indem Sie den niedrigsten Implementierungscode schreiben, den Sie in der Testmethode haben können .
  4. Refaktor zum Entfernen von Duplikaten und ansonsten nach Bedarf zum Verbessern des Designs. Sei streng mit diesen Zügen:
    1. Sie möchten eine neue Methode - warten Sie bis zum Refactoring und… erstellen Sie neue (nicht testbezogene) Methoden, indem Sie eine der folgenden Aktionen ausführen, und auf keine andere Weise:
      • bevorzugt: Extrahieren Sie die Methode anhand des gemäß (3) erstellten Implementierungscodes, um eine neue Methode in der Testklasse zu erstellen, oder
      • wenn Sie müssen: Verschieben Sie den Implementierungscode gemäß (3) in eine vorhandene Implementierungsmethode
    2. Sie möchten eine neue Klasse - warten Sie bis zum Refactoring, und erstellen Sie dann Nicht-Test-Klassen, um ein Ziel für eine Verschiebungsmethode bereitzustellen, und ohne weiteren Grund
    3. Füllen Sie Implementierungsklassen mit Methoden, indem Sie Move Method ausführen, und auf keine andere Weise

In der Regel führt dies zu sehr unterschiedlichen Designs als die häufig praktizierte "Pseudo-TDD-Methode", bei der Sie sich vorstellen, wie das Design aussehen soll. Schreiben Sie dann Tests, um dieses Design zu erzwingen, und implementieren Sie das Design, das Sie sich bereits vorgestellt haben, bevor Sie Ihr Design schreiben Tests ".

Wenn eine Gruppe von Leuten so etwas wie ein Tic Tac Toe-Spiel mit Pseudo-TDD implementiert, erhalten sie normalerweise sehr ähnliche Designs, die eine Art BoardKlasse mit einem 3 × 3-Array von Integers beinhalten. Und zumindest ein Teil der Programmierer wird diese Klasse tatsächlich ohne Tests dafür geschrieben haben, weil sie "wissen, dass sie es brauchen werden" oder "etwas brauchen, gegen das sie ihre Tests schreiben können". Wenn Sie jedoch dieselbe Gruppe dazu zwingen, TDD anzuwenden, als ob Sie es beabsichtigen, ergeben sich häufig eine große Vielfalt sehr unterschiedlicher Entwürfe, die häufig nichts verwenden, was auch nur annähernd a ähnelt Board.

Können Sie feststellen, ob der von Ihnen geschriebene Test ausreicht?

Wenn sie alle geschäftlichen Anforderungen abdecken. Tests sind eine Kodierung der Systemanforderungen.

Ist es empfehlenswert, Tests für sehr einfache Funktionen zu schreiben, die 1 + 1 = 2 entsprechen könnten, oder handelt es sich nur um ein Übermaß?

Wieder haben Sie es rückwärts: Sie schreiben keine Tests für die Funktionalität. Sie schreiben Funktionen für Tests. Wenn sich herausstellt, dass die Funktionalität, um den Test zu bestehen, trivial ist, ist das großartig! Sie haben gerade eine Systemanforderung erfüllt und mussten nicht einmal hart dafür arbeiten!

Ist es gut, die Funktionalität zu ändern und entsprechend zu testen, ob sich die Anforderung ändert?

Nein, anders herum. Wenn sich eine Anforderung ändert, ändern Sie den Test, der dieser Anforderung entspricht. Beobachten Sie, wie er fehlschlägt, und ändern Sie dann den Code, damit er erfolgreich ist. Die Tests stehen immer an erster Stelle.

Das ist schwer zu machen. Sie brauchen Dutzende, vielleicht Hunderte von Stunden absichtlichen Übens , um eine Art "Muskelgedächtnis" aufzubauen, um zu einem Punkt zu gelangen, an dem Sie, wenn die Frist abläuft und Sie unter Druck stehen, nicht einmal mehr darüber nachdenken müssen und dies zu tun wird die schnellste und natürlichste Art zu arbeiten.

Jörg W. Mittag
quelle
1
Eine sehr klare Antwort! Aus praktischer Sicht ist ein flexibles und leistungsfähiges Test-Framework beim Üben von TDD sehr angenehm. Unabhängig von TDD ist die Fähigkeit, Tests automatisch auszuführen, von unschätzbarem Wert, um eine Anwendung zu debuggen. Um mit TDD zu beginnen, sind nicht-interaktive Programme (im UNIX-Stil) wahrscheinlich die einfachsten, da ein Anwendungsfall getestet werden kann, indem der Exit-Status und die Ausgabe des Programms mit den Erwartungen verglichen werden. Ein konkretes Beispiel für diesen Ansatz finden Sie in meiner Benzinbibliothek für OCaml.
Michael Le Barbier Grünewald
4
Sie sagen, "wenn Sie dieselbe Gruppe zwingen, TDD anzuwenden, als ob Sie es beabsichtigen, werden sie häufig mit einer großen Vielfalt von sehr unterschiedlichen Designs enden und oft nichts verwenden, das einem Board auch nur annähernd ähnlich ist", als wäre es eine gute Sache . Mir ist überhaupt nicht klar, dass es eine gute Sache ist und vom Standpunkt der Wartung aus sogar schlecht sein kann, da es so aussieht, als wäre die Implementierung für jemanden, der neu ist, sehr kontraintuitiv. Können Sie erklären, warum diese Implementierungsvielfalt eine gute oder zumindest keine schlechte Sache ist?
Jim Clay
3
+1 Die Antwort ist insofern gut, als sie TDD korrekt beschreibt. Es zeigt jedoch auch, warum TDD eine fehlerhafte Methodik ist: Sorgfältiges Denken und explizites Design sind erforderlich, insbesondere bei algorithmischen Problemen. TDD "im blinden" zu machen (wie es TDD vorschreibt), indem man vorgibt, keine Domänenkenntnisse zu haben, führt zu unnötigen Schwierigkeiten und Sackgassen. Sehen Sie das berüchtigte Sudoku-Löser-Debakel (Kurzversion: TDD kann Domain-Kenntnisse nicht übertreffen).
Andres F.
1
@AndresF .: Eigentlich Sie die Blog - Post verknüpfen die Erfahrungen Keith gemacht , wenn dabei TDD Wie zu Echo scheint , wenn Sie es Meant: wenn „pseudo-TDD“ für Tic-Tac-Toe tun, beginnen sie durch die Schaffung einer BoardKlasse mit einem 3x3 Array von ints (oder so ähnlich). Wenn Sie sie zu TDDAIYMI zwingen, wird häufig eine Mini-DSL zum Erfassen des Domänenwissens erstellt. Das ist natürlich nur anekdotisch. Eine statistisch und wissenschaftlich fundierte Studie wäre schön, aber wie so oft, sind sie entweder viel zu klein oder viel zu teuer.
Jörg W Mittag
@ JörgWMittag Korrigiere mich, wenn ich dich missverstanden habe, aber sagst du, dass Ron Jeffries "Pseudo-TDD" gemacht hat? Ist das nicht eine Form des "Nein, wahren Schotten" -Fehlschlusses? (Ich stimme Ihnen zu, dass mehr wissenschaftliche Studien erforderlich sind. Der Blog, auf den ich verlinkt habe, ist nur eine bunte Anekdote über das spektakuläre Scheitern einer bestimmten TDD-Nutzung. Leider scheinen die TDD-Evangelisten für den Rest zu laut zu sein von uns, um eine echte Analyse dieser Metholodie und ihrer angeblichen Vorteile zu haben).
Andres F.
5

Sie beschreiben Ihren Entwicklungsansatz als "nur von oben nach unten" - Sie gehen von einer höheren Abstraktionsebene aus und gehen immer mehr ins Detail. TDD ist, zumindest in der Form, wie es beliebt ist, eine "Bottom-up" -Technik. Und für jemanden, der hauptsächlich von oben nach unten arbeitet, kann es in der Tat sehr ungewöhnlich sein, von unten nach oben zu arbeiten.

Wie können Sie also mehr "TDD" in Ihren Entwicklungsprozess bringen? Erstens gehe ich davon aus, dass Ihr tatsächlicher Entwicklungsprozess nicht immer so "top-down" ist, wie Sie es oben beschrieben haben. Nach Schritt 2 haben Sie wahrscheinlich einige Komponenten identifiziert, die von anderen Komponenten unabhängig sind. Manchmal entscheiden Sie sich, diese Komponenten zuerst zu implementieren. Die Details der öffentlichen API dieser Komponenten entsprechen wahrscheinlich nicht nur Ihren Anforderungen, sondern auch Ihren Entwurfsentscheidungen. Hier können Sie mit TDD beginnen: Stellen Sie sich vor, wie Sie die Komponente verwenden und wie Sie die API tatsächlich verwenden. Und wenn Sie damit beginnen, eine solche API-Verwendung in Form eines Tests zu codieren, haben Sie gerade mit TDD begonnen.

Zweitens können Sie TDD auch dann ausführen, wenn Sie mehr von oben nach unten codieren, beginnend mit Komponenten, die zuerst von anderen, nicht vorhandenen Komponenten abhängig sind. Was Sie lernen müssen, ist, wie Sie diese anderen Abhängigkeiten zuerst "verspotten". Auf diese Weise können Sie übergeordnete Komponenten erstellen und testen, bevor Sie zu den untergeordneten Komponenten wechseln. Ein sehr detailliertes Beispiel zur Top-Down-TDD finden Sie in diesem Blog-Beitrag von Ralf Westphal .

Doc Brown
quelle
3

Mache ich es richtig? Wenn nicht was genau muss ich ändern

Es geht dir gut.

Können Sie feststellen, ob der von Ihnen geschriebene Test ausreicht?

Ja, verwenden Sie ein Test- / Code-Coverage-Tool . Martin Fowler bietet einige gute Tipps zur Testabdeckung.

Ist es empfehlenswert, Tests für sehr einfache Funktionen zu schreiben, die 1 + 1 = 2 entsprechen könnten, oder handelt es sich nur um ein Übermaß?

Im Allgemeinen ist jede Funktion, Methode, Komponente usw., von der Sie bei bestimmten Eingaben ein Ergebnis erwarten, ein guter Kandidat für einen Komponententest. Wie bei den meisten Dingen im (technischen) Leben müssen Sie jedoch Ihre Kompromisse berücksichtigen: Wird der Aufwand durch das Schreiben des Komponententests ausgeglichen, was auf lange Sicht zu einer stabileren Codebasis führt? Im Allgemeinen sollten Sie zuerst Testcode für wichtige / kritische Funktionen schreiben. Wenn Sie später feststellen, dass Fehler mit einem nicht getesteten Teil des Codes zusammenhängen, fügen Sie weitere Tests hinzu.

Ist es gut, die Funktionalität zu ändern und entsprechend zu testen, ob sich die Anforderung ändert?

Das Gute an automatisierten Tests ist, dass Sie sofort sehen, ob eine Änderung frühere Behauptungen verletzt. Wenn Sie dies aufgrund geänderter Anforderungen erwarten, ist es in Ordnung, den Testcode zu ändern (tatsächlich würden Sie in reinem TDD die Tests zuerst entsprechend den Anforderungen ändern und dann den Code übernehmen, bis er den neuen Anforderungen entspricht).

miraculixx
quelle
Die Codeabdeckung ist möglicherweise kein sehr zuverlässiges Maß. Die Durchsetzung von% der Abdeckung führt in der Regel zu vielen nicht erforderlichen Tests (z. B. Tests für alle Parameter, Nullprüfungen usw.) und zu einer Verschwendung von Entwicklungszeit, während Code nur schwer zu testen ist Pfade werden möglicherweise überhaupt nicht getestet.
Paul
3

Das Schreiben von Tests ist ein völlig anderer Ansatz zum Schreiben von Software. Tests sind nicht nur ein Werkzeug für die ordnungsgemäße Überprüfung der Codefunktionalität (sie bestehen alle), sondern auch die Kraft, die das Design definiert. Auch wenn die Testabdeckung eine nützliche Messgröße sein mag, darf sie nicht das eigentliche Ziel sein - das Ziel von TDD besteht nicht darin, einen guten Prozentsatz der Codeabdeckung zu erreichen, sondern vor dem Schreiben über die Testbarkeit des Codes nachzudenken.

Wenn Sie Probleme mit dem Schreiben von Tests haben, kann ich Ihnen nur empfehlen, eine Pair-Programming-Sitzung mit jemandem zu absolvieren, der Erfahrung mit TDD hat, damit Sie die "Denkweise" des gesamten Ansatzes kennenlernen können.

Eine weitere gute Sache ist es, Online-Videos anzusehen, bei denen Software mit TDD von Anfang an entwickelt wird. Gut, dass ich mich einmal mit TDD bekannt gemacht habe, war Let's Play TDD von James Shore. Werfen Sie einen Blick darauf, es wird veranschaulichen, wie aufstrebendes Design funktioniert, welche Fragen Sie sich beim Schreiben von Tests stellen sollten und wie neue Klassen und Methoden erstellt, überarbeitet und iteriert werden.

Können Sie feststellen, ob der von Ihnen geschriebene Test ausreicht?

Ich glaube, das ist die falsche Frage. Bei TDD haben Sie sich für TDD und Emergent Design als Methode zum Schreiben von Software entschieden. Wenn eine neue Funktionalität, die Sie hinzufügen müssen, immer mit einem Test beginnt, ist sie immer verfügbar.

Ist es empfehlenswert, Tests für sehr einfache Funktionen zu schreiben, die 1 + 1 = 2 entsprechen könnten, oder handelt es sich nur um ein Übermaß?

Es hängt natürlich davon ab, wie Sie es beurteilen. Ich bevorzuge es, keine Tests für Parameter zu schreiben, die keine Prüfungen enthalten, wenn die Methode nicht Teil der öffentlichen API ist. Warum sollten Sie andernfalls nicht bestätigen, dass die Methode Add (a, b) tatsächlich a + b zurückgibt?

Ist es gut, die Funktionalität zu ändern und entsprechend zu testen, ob sich die Anforderung ändert?

Wenn Sie Ihren Code ändern oder neue Funktionen hinzufügen, beginnen Sie mit einem Test, unabhängig davon, ob ein neuer Test hinzugefügt oder ein vorhandener Test geändert wird, wenn sich die Anforderungen ändern.

Paul
quelle