it-swarm.com.de

Wann schreiben Sie den "echten" Code in TDD?

Alle Beispiele, die ich in Schulungsvideos gelesen und gesehen habe, enthalten vereinfachende Beispiele. Aber was ich nicht sehe, wenn ich den "echten" Code mache, nachdem ich grün geworden bin. Ist das der "Refactor" Teil?

Wenn ich ein ziemlich komplexes Objekt mit einer komplexen Methode habe und meinen Test und das Nötigste schreibe, um ihn zu bestehen (nachdem er zum ersten Mal fehlgeschlagen ist, Rot). Wann gehe ich zurück und schreibe den richtigen Code? Und wie viel echten Code schreibe ich, bevor ich erneut teste? Ich vermute, dass der letzte mehr Intuition ist.

Edit : Danke an alle, die geantwortet haben. Alle Ihre Antworten haben mir sehr geholfen. Es scheint unterschiedliche Vorstellungen darüber zu geben, worüber ich gefragt oder verwirrt habe, und vielleicht gibt es sie auch, aber ich habe gefragt, ob ich einen Antrag für den Bau einer Schule habe.

In meinem Design habe ich eine Architektur, mit der ich beginnen möchte, User Stories usw. Von hier aus nehme ich diese User Stories und erstelle einen Test, um die User Story zu testen. Der Benutzer sagt: Wir haben Leute, die sich für die Schule anmelden und Anmeldegebühren zahlen. Also denke ich über einen Weg nach, um dies zum Scheitern zu bringen. Dabei entwerfe ich eine Testklasse für Klasse X (vielleicht Student), die fehlschlagen wird. Ich erstelle dann die Klasse "Student". Vielleicht "Schule" weiß ich nicht.

Aber auf jeden Fall zwingt mich das TD Design ), die Geschichte zu durchdenken. Wenn ich eine machen kann Test fehlgeschlagen, ich weiß, warum es fehlschlägt, aber dies setzt voraus, dass ich es bestehen kann. Es geht um das Entwerfen.

Ich vergleiche das mit dem Nachdenken über Rekursion. Rekursion ist kein schwieriges Konzept. Es mag schwieriger sein, den Überblick zu behalten, aber in Wirklichkeit ist es am schwierigsten zu wissen, wann die Rekursion "bricht", wann man aufhört (meine Meinung natürlich). Also muss ich darüber nachdenken, was aufhört die Rekursion zuerst. Es ist nur eine unvollständige Analogie und es wird angenommen, dass jede rekursive Iteration ein "Durchgang" ist. Wieder nur eine Meinung.

In der Umsetzung ist die Schule schwerer zu sehen. Numerische und Bankbücher sind "einfach" in dem Sinne, dass Sie einfache Arithmetik verwenden können. Ich kann a + b sehen und 0 zurückgeben usw. Bei einem System von Menschen muss ich mir genauer überlegen, wie ich das implementieren kann . Ich habe das Konzept des Scheiterns, Bestehens, Refaktors (hauptsächlich wegen des Studiums und dieser Frage).

Was ich nicht weiß, beruht meiner Meinung nach auf mangelnder Erfahrung. Ich weiß nicht, wie ich einen neuen Studenten nicht anmelden kann. Ich weiß nicht, wie ich jemanden scheitern lassen kann, der einen Nachnamen eingibt und dieser in einer Datenbank speichert. Ich weiß, wie man eine + 1 für einfache Mathematik macht, aber bei Entitäten wie einer Person weiß ich nicht, ob ich nur teste, ob ich eine eindeutige Datenbank-ID oder etwas anderes zurückerhalte, wenn jemand einen Namen in a eingibt Datenbank oder beides oder keines.

Oder vielleicht zeigt dies, dass ich immer noch verwirrt bin.

150
johnny

Wenn ich ein ziemlich komplexes Objekt mit einer komplexen Methode habe und meinen Test und das Nötigste schreibe, um ihn zu bestehen (nachdem er zum ersten Mal fehlgeschlagen ist, Rot). Wann gehe ich zurück und schreibe den richtigen Code? Und wie viel echten Code schreibe ich, bevor ich erneut teste? Ich vermute, dass der letzte mehr Intuition ist.

Sie "gehen nicht zurück" und schreiben "echten Code". Es ist alles echter Code. Was Sie tun, ist zurück zu gehen und einen weiteren Test hinzuzufügen, der zwingt Sie zu ändern Ihren Code, um den neuen Test zu bestehen.

Wie viel Code schreiben Sie, bevor Sie erneut testen? Keiner. Sie schreiben Null Code ohne einen fehlgeschlagenen Test, der zwingt Sie, mehr Code zu schreiben.

Beachten Sie das Muster?

Lassen Sie uns durch (ein weiteres) einfaches Beispiel gehen, in der Hoffnung, dass es hilft.

Assert.Equal("1", FizzBuzz(1));

Einfach peazy.

public String FizzBuzz(int n) {
    return 1.ToString();
}

Nicht das, was Sie echten Code nennen würden, oder? Fügen wir einen Test hinzu, der eine Änderung erzwingt.

Assert.Equal("2", FizzBuzz(2));

Wir könnten etwas Dummes tun wie if n == 1, aber wir springen zur vernünftigen Lösung.

public String FizzBuzz(int n) {
    return n.ToString();
}

Cool. Dies funktioniert für alle Nicht-FizzBuzz-Nummern. Was ist die nächste Eingabe, die eine Änderung des Produktionscodes erzwingt?

Assert.Equal("Fizz", FizzBuzz(3));

public String FizzBuzz(int n) {
    if (n == 3)
        return "Fizz";
    return n.ToString();
}

Und wieder. Schreiben Sie einen Test, der noch nicht bestanden wird.

Assert.Equal("Fizz", FizzBuzz(6));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    return n.ToString();
}

Und wir haben jetzt alle Vielfachen von drei abgedeckt (das sind nicht auch Vielfache von fünf, wir werden es notieren und zurückkommen).

Wir haben noch keinen Test für "Buzz" geschrieben, also schreiben wir das.

Assert.Equal("Buzz", FizzBuzz(5));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    if (n == 5)
        return "Buzz"
    return n.ToString();
}

Und wieder wissen wir, dass wir noch einen anderen Fall behandeln müssen.

Assert.Equal("Buzz", FizzBuzz(10));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    if (n % 5 == 0)
        return "Buzz"
    return n.ToString();
}

Und jetzt können wir alle Vielfachen von 5 behandeln, die nicht auch Vielfache von 3 sind.

Bis zu diesem Punkt haben wir den Refactoring-Schritt ignoriert, aber ich sehe einige Überschneidungen. Lassen Sie uns das jetzt aufräumen.

private bool isDivisibleBy(int divisor, int input) {
    return (input % divisor == 0);
}

public String FizzBuzz(int n) {
    if (isDivisibleBy(3, n))
        return "Fizz";
    if (isDivisibleBy(5, n))
        return "Buzz"
    return n.ToString();
}

Cool. Jetzt haben wir die Duplizierung entfernt und eine gut benannte Funktion erstellt. Was ist der nächste Test, den wir schreiben können, der uns zwingt, den Code zu ändern? Nun, wir haben den Fall vermieden, in dem die Zahl durch 3 und 5 teilbar ist. Schreiben wir sie jetzt.

Assert.Equal("FizzBuzz", FizzBuzz(15));

public String FizzBuzz(int n) {
    if (isDivisibleBy(3, n) && isDivisibleBy(5, n))
        return "FizzBuzz";
    if (isDivisibleBy(3, n))
        return "Fizz";
    if (isDivisibleBy(5, n))
        return "Buzz"
    return n.ToString();
}

Die Tests bestehen, aber wir haben mehr Duplikate. Wir haben Optionen, aber ich werde "Lokale Variable extrahieren" einige Male anwenden, damit wir das Refactoring durchführen, anstatt es neu zu schreiben.

public String FizzBuzz(int n) {

    var isDivisibleBy3 = isDivisibleBy(3, n);
    var isDivisibleBy5 = isDivisibleBy(5, n);

    if ( isDivisibleBy3 && isDivisibleBy5 )
        return "FizzBuzz";
    if ( isDivisibleBy3 )
        return "Fizz";
    if ( isDivisibleBy5 )
        return "Buzz"
    return n.ToString();
}

Und wir haben alle vernünftigen Eingaben abgedeckt, aber was ist mit nvernünftigen Eingaben? Was passiert, wenn wir 0 oder ein Negativ übergeben? Schreiben Sie diese Testfälle.

public String FizzBuzz(int n) {

    if (n < 1)
        throw new InvalidArgException("n must be >= 1);

    var isDivisibleBy3 = isDivisibleBy(3, n);
    var isDivisibleBy5 = isDivisibleBy(5, n);

    if ( isDivisibleBy3 && isDivisibleBy5 )
        return "FizzBuzz";
    if ( isDivisibleBy3 )
        return "Fizz";
    if ( isDivisibleBy5 )
        return "Buzz"
    return n.ToString();
}

Fängt das schon an, wie "echter Code" auszusehen? Noch wichtiger ist, an welchem ​​Punkt hörte es auf, "unwirklicher Code" zu sein und ging zu "real" über? Das ist etwas zum Nachdenken ...

Ich konnte dies einfach tun, indem ich nach einem Test suchte, von dem ich wusste, dass er nicht bei jedem Schritt bestehen würde, aber ich hatte viel Übung. Wenn ich bei der Arbeit bin, sind die Dinge nie so einfach und ich weiß möglicherweise nicht immer, welcher Test eine Änderung erzwingen wird. Manchmal schreibe ich einen Test und bin überrascht zu sehen, dass er bereits bestanden hat! Ich empfehle Ihnen dringend, sich daran zu gewöhnen, eine "Testliste" zu erstellen, bevor Sie beginnen. Diese Testliste sollte alle "interessanten" Eingaben enthalten, die Sie sich vorstellen können. Sie werden möglicherweise nicht alle verwenden und wahrscheinlich unterwegs Fälle hinzufügen, aber diese Liste dient als Roadmap. Meine Testliste für FizzBuzz würde ungefähr so ​​aussehen.

  • Negativ
  • Null
  • Eins
  • Zwei
  • Drei
  • Vier
  • Fünf
  • Sechs (nicht triviales Vielfaches von 3)
  • Neun (3 im Quadrat)
  • Zehn (nicht triviales Vielfaches von 5)
  • 15 (Vielfaches von 3 & 5)
  • 30 (nicht triviales Vielfaches von 3 & 5)
241
RubberDuck

Der "echte" Code ist der Code, den Sie schreiben, um Ihren Test zu bestehen. Wirklich . So einfach ist das.

Wenn Leute darüber reden, das Nötigste zu schreiben, um den Test grün zu machen, bedeutet das nur, dass Ihr realer Code dem YAGNI-Prinzip folgen sollte.

Die Idee des Refactor-Schritts besteht darin, das, was Sie geschrieben haben, zu bereinigen, sobald Sie zufrieden sind, dass es die Anforderungen erfüllt.

Solange die von Ihnen geschriebenen Tests tatsächlich Ihre Produktanforderungen umfassen, ist der Code nach dem Bestehen vollständig. Denken Sie darüber nach, wenn alle Ihre Geschäftsanforderungen einen Test haben und alle diese Tests grün sind, was gibt es noch zu schreiben? (Okay, im wirklichen Leben haben wir normalerweise keine vollständige Testabdeckung, aber die Theorie ist solide.)

45
GenericJon

Die kurze Antwort lautet, dass der "echte Code" der Code ist, der den Test besteht. Wenn Sie Ihren Test mit etwas anderem als echtem Code bestehen können, fügen Sie weitere Tests hinzu!

Ich bin damit einverstanden, dass viele Tutorials zu TDD simpel sind. Das wirkt gegen sie. Ein zu einfacher Test für eine Methode, die beispielsweise 3 + 8 berechnet, hat wirklich keine andere Wahl, als auch 3 + 8 zu berechnen und das Ergebnis zu vergleichen. Das sieht so aus, als würden Sie nur Code überall duplizieren, und das Testen ist sinnlos und fehleranfällig.

Wenn Sie gut testen können, erfahren Sie, wie Sie Ihre Anwendung strukturieren und wie Sie Ihren Code schreiben. Wenn Sie Probleme haben, vernünftige und hilfreiche Tests zu entwickeln, sollten Sie Ihr Design wahrscheinlich ein wenig überdenken. Ein gut konzipiertes System ist einfach zu testen - das heißt, sinnvolle Tests sind leicht zu denken und zu implementieren.

Wenn Sie Ihre Tests zuerst schreiben, beobachten, wie sie fehlschlagen, und dann den Code schreiben, der sie bestanden hat. Dies ist eine Disziplin, um sicherzustellen, dass Ihr gesamter Code entsprechende Tests enthält. Ich folge dieser Regel nicht sklavisch, wenn ich codiere. oft schreibe ich Tests nachträglich. Aber zuerst Tests durchzuführen hilft Ihnen, ehrlich zu bleiben. Mit etwas Erfahrung werden Sie feststellen, wenn Sie sich in eine Ecke codieren, auch wenn Sie nicht zuerst Tests schreiben.

14
Carl Raymond

Manchmal können einige Beispiele zu TDD irreführend sein. Wie andere bereits erwähnt haben, ist der Code, den Sie schreiben, um die Tests zu bestehen, der echte Code.

Aber denken Sie nicht, dass der echte Code wie Magie erscheint - das ist falsch. Sie müssen besser verstehen, was Sie erreichen möchten, und dann müssen Sie den Test entsprechend auswählen, beginnend mit den einfachsten Fällen und Eckfällen.

Wenn Sie beispielsweise einen Lexer schreiben müssen, beginnen Sie mit einer leeren Zeichenfolge, dann mit einer Reihe von Leerzeichen, dann einer Zahl, dann mit einer von Leerzeichen umgebenen Zahl, dann einer falschen Zahl usw. Diese kleinen Transformationen führen Sie zu Der richtige Algorithmus, aber Sie springen nicht vom einfachsten Fall zu einem hochkomplexen Fall, der dumm ausgewählt wurde, um den eigentlichen Code zu erstellen.

Bob Martin erklärt es perfekt hier .

6
Victor Cejudo

Der Refactor-Teil wird aufgeräumt, wenn Sie müde sind und nach Hause gehen möchten.

Wenn Sie eine Funktion hinzufügen möchten, ändern Sie den Refactor-Teil vor dem nächsten Test. Sie überarbeiten den Code, um Platz für die neue Funktion zu schaffen. Sie tun dies, wenn Sie wissen, was diese neue Funktion sein wird. Nicht, wenn du es dir nur vorstellst.

Dies kann so einfach sein wie das Umbenennen von GreetImpl in GreetWorld, bevor Sie eine GreetMom -Klasse erstellen (nachdem Sie einen Test hinzugefügt haben), um eine Funktion hinzuzufügen, die "Hi Mom" ​​druckt.

5
candied_orange

Sie schreiben die ganze Zeit Real Code.

Bei jedem Schritt schreiben Sie Code, um die Bedingungen zu erfüllen, die Ihr Code für zukünftige Anrufer Ihres Codes erfüllt (die Sie sein können oder nicht ...).

Sie denken, Sie schreiben keinen nützlichen ( real) Code, weil Sie ihn in einem Moment möglicherweise umgestalten.

Code-Refactoring ist der Prozess der Umstrukturierung des vorhandenen Computercodes - Änderung des Factorings - ohne Änderung des externen Verhaltens.

Dies bedeutet, dass die Bedingungen, unter denen der Code zufrieden ist, unverändert bleiben, obwohl Sie den Code ändern. Und die Überprüfungen ( Tests), die Sie implementiert haben, um zu überprüfen, ob Ihr Code bereits vorhanden ist, um zu überprüfen, ob Ihre Änderungen etwas geändert haben. Der Code, den Sie die ganze Zeit geschrieben haben, ist also da, nur auf eine andere Art und Weise.

Ein weiterer Grund, warum Sie vielleicht denken, dass es sich nicht um echten Code handelt, ist, dass Sie Beispiele machen, bei denen das Endprogramm bereits von Ihnen vorhergesehen werden kann. Dies ist sehr gut, da es zeigt, dass Sie Kenntnisse über die Domain haben, in der Sie programmieren.
Aber oft befinden sich Programmierer in einer Domäne, die für sie neu, unbekannt ist. Sie wissen nicht, wie das Endergebnis aussehen wird, und TDD ist eine Technik, um Schritt für Schritt Programme zu schreiben und unser Wissen darüber zu dokumentieren, wie dieses System funktionieren soll und zu überprüfen, ob unser Code so funktioniert.

Als ich The Book (*) auf TDD las, war für mich das wichtigste Merkmal die: TODO-Liste. Es hat mir gezeigt, dass TDD auch eine Technik ist, mit der Entwickler sich jeweils auf eine Sache konzentrieren können. Dies ist also auch eine Antwort auf Ihre Frage zu Wie viel echten Code muss geschrieben werden? Ich würde genug Code sagen, um mich auf jeweils eine Sache zu konzentrieren.

(*) "Test Driven Development: By Example" von Kent Beck

1

Der reale Code würde jedoch in der Refactor-Phase der TDD-Phase erscheinen. Das heißt, der Code, der Teil der endgültigen Version sein sollte.

Tests sollten jedes Mal ausgeführt werden, wenn Sie eine Änderung vornehmen.

Das Motto des TDD-Lebenszyklus wäre: RED GREEN REFACTOR

[~ # ~] rot [~ # ~] : Schreiben Sie die Tests

[~ # ~] grün [~ # ~] : Machen Sie einen ehrlichen Versuch, Funktionscode zu erhalten, der die Tests so schnell wie möglich besteht: Code duplizieren, dunkel benannte Variablen-Hacks höchster Ordnung usw.

[~ # ~] Refactor [~ # ~] : Bereinigen Sie den Code und benennen Sie die Variablen richtig. TROCKEN den Code hoch.

1
graeme

Wann schreiben Sie den "echten" Code in TDD?

In der Phase rot schreiben Sie Code.

In der Phase Refactoring Ist das primäre Ziel, Code zu löschen.

In der Phase rot tun Sie alles, um den Test zu bestehen so schnell wie möglich und um jeden Preis. Sie ignorieren völlig, was Sie jemals von guten Codierungspraktiken oder Entwurfsmustern gehört haben. Es ist alles, was zählt, den Test grün zu machen.

In der Phase Refactoring Räumen Sie das Chaos auf, das Sie gerade angerichtet haben. Jetzt schauen Sie zuerst, ob die Änderung, die Sie gerade vorgenommen haben, die oberste in der Liste der Transformationsprioritäten ist und ob es eine Codeduplizierung gibt, die Sie höchstwahrscheinlich durch Anwenden eines Entwurfsmusters entfernen können.

Schließlich verbessern Sie die Lesbarkeit, indem Sie Bezeichner umbenennen und magische Zahlen und/oder Literalzeichenfolgen in Konstanten extrahieren.


Es ist kein Rot-Refaktor, es ist ein Rot-Grün-Refaktor. - Rob Kinyon

Vielen Dank für den Hinweis.

Es ist also die grün Phase, in der Sie den echten Code schreiben

In der Phase rot schreiben Sie die ausführbare Spezifikation ...

1
Timothy Truckle

Sie schreiben keinen Code, damit Ihre Tests fehlschlagen.

Sie schreiben Ihre Tests, um zu definieren, wie Erfolg aussehen soll. Dies sollte zunächst alles fehlschlagen, da Sie den Code, der übergeben wird, noch nicht geschrieben haben.

Der springende Punkt beim Schreiben von anfänglich fehlgeschlagenen Tests besteht darin, zwei Dinge zu tun:

  1. Decken Sie alle Fälle ab - alle nominellen Fälle, alle Edge-Fälle usw.
  2. Validieren Sie Ihre Tests. Wenn Sie nur sehen, dass sie vergehen, wie können Sie dann sicher sein, dass sie einen Fehler zuverlässig melden, wenn einer auftritt?

Der Punkt hinter Rot-Grün-Refaktor ist, dass Sie durch das Schreiben der richtigen Tests zuerst die Gewissheit haben, dass der Code, den Sie zum Bestehen der Tests geschrieben haben, korrekt ist, und dass Sie mit der Gewissheit überarbeiten können, dass Ihre Tests Sie so schnell wie möglich informieren etwas bricht, so dass Sie sofort zurückgehen und es reparieren können.

Nach meiner eigenen Erfahrung (C # /. NET) ist reines Testen zuerst ein unerreichbares Ideal, da Sie keinen Aufruf einer Methode kompilieren können, die noch nicht existiert. Bei "Test zuerst" geht es also wirklich darum, zuerst Schnittstellen zu codieren und Implementierungen zu stubben und dann Tests gegen die Stubs zu schreiben (die anfänglich fehlschlagen), bis die Stubs richtig ausgearbeitet sind. Ich schreibe nie "fehlerhaften Code", sondern baue nur aus Stubs heraus.

1
Zenilogix

Ich denke, Sie können zwischen Unit-Tests und Integrationstests verwechselt werden. Ich glaube, es kann auch Abnahmetests geben, aber das hängt von Ihrem Prozess ab.

Sobald Sie alle kleinen "Einheiten" getestet haben, testen Sie sie alle zusammengebaut oder "integriert". Das ist normalerweise ein ganzes Programm oder eine ganze Bibliothek.

In Code, den ich für die Integration geschrieben habe, wird eine Bibliothek mit verschiedenen Testprogrammen getestet, die Daten lesen und in die Bibliothek einspeisen. Überprüfen Sie dann die Ergebnisse. Dann mache ich es mit Threads. Dann mache ich es mit Fäden und Gabel () in der Mitte. Dann starte ich es und töte -9 nach 2 Sekunden, dann starte ich es und überprüfe seinen Wiederherstellungsmodus. Ich fuzz es. Ich foltere es auf alle möglichen Arten.

All das ist AUCH ein Test, aber ich habe kein hübsches rot/grünes Display für die Ergebnisse. Entweder gelingt es, oder ich gehe ein paar tausend Zeilen Fehlercode durch, um herauszufinden, warum.

Dort testen Sie den "echten Code".

Und ich habe gerade darüber nachgedacht, aber vielleicht wissen Sie nicht, wann Sie mit dem Schreiben von Unit-Tests fertig sein sollen. Sie sind mit dem Schreiben von Komponententests fertig, wenn Ihre Tests alles ausführen, was Sie angegeben haben. Manchmal verlieren Sie den Überblick über alle Fehlerbehandlungs- und Edge-Fälle. Daher möchten Sie möglicherweise eine Nice-Testgruppe mit Happy-Path-Tests erstellen, die einfach direkt durch die Spezifikationen gehen.

0
Zan Lynx