Wie baby sind deine Babyschritte bei TDD?

37

Heute haben wir TDD trainiert und dabei folgende Missverständnisse festgestellt.

Die Aufgabe ist für die Eingabe "1,2" eine Rückgabesumme von 3 Zahlen. Was ich geschrieben habe (in C #) war:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]); //task said we have two numbers and input is correct

Aber andere Leute zogen es vor, es anders zu machen. Zunächst wurde für die Eingabe "1,2" der folgende Code hinzugefügt:

if (input == "1,2")
   return 3;

Dann führten sie einen weiteren Test für die Eingabe "4,5" ein und änderten die Implementierung:

if (input == "1,2")
   return 3;
else if (input == "4,5")
   return 9;

Und danach sagten sie "Okay, jetzt sehen wir das Muster" und setzten das um, was ich anfangs getan hatte.

Ich denke, der zweite Ansatz passt besser zur TDD-Definition, aber ... sollten wir so streng damit umgehen? Für mich ist es in Ordnung, kleine Schritte zu überspringen und sie zu "Doppelschritten" zu kombinieren, wenn ich mir sicher bin, dass ich nichts überspringen werde. Liege ich falsch?

Aktualisieren. Ich habe einen Fehler gemacht, indem ich nicht klargestellt habe, dass es nicht der erste Test war. Es gab bereits einige Tests, so dass "return 3" eigentlich nicht der einfachste Code war, um die Anforderung zu erfüllen.

SiberianGuy
quelle
25
So winzig, dass meine Mitarbeiter "Ooahhh dazso cuuuuuute" sprudeln
Adel
6
@Adel: Fast erstickt an meinem Frühstück, Tastatur jetzt voll oder Spucke und Krümel
Binary Worrier
2
@Adel, was nicht Muttersprachler betrifft, ist es für mich ziemlich schwierig, diesen Humor zu verstehen, aber ich denke, Ihre Mitarbeiter mögen die Frage :)
SiberianGuy
8
@Idsa: Es transponiert eine Antwort von Mitarbeitern, wenn die ersten Schritte eines Kindes "Ooahhh dazso cuuuuuute" = "Oh, das ist so süß" (gesprochen in einem Gesang, der nicht so süß ist) mit ihrer Antwort gezeigt werden Wenn sie Unit Tests von Adel sehen und die Babyschritte von Unit Tests betrachten, sagen sie "Oh, das ist so süß". Reaktion auf ein - echtes - Baby - Schritte = Reaktion auf Unit - Tests "Baby - Schritte".
Binary Worrier
3
@ Binaryworrier wünschte, ich könnte Ihnen echte Punkte für die Zeit geben, um die Eltern zu erklären
Andrew T Finnell

Antworten:

31

Schreiben Sie den einfachsten Code, mit dem die Tests bestanden werden.

Soweit ich sehen kann, hat das keiner von Ihnen getan.

Baby Schritt 1.

Test: Geben Sie für die Eingabe "1,2" die Summe der Zahlen 3 zurück

Machen Sie den Test fehlgeschlagen:

throw NotImplementedException();

Machen Sie den Testdurchlauf:

return 3;

Baby Schritt 2.

Test: Für die Eingabe "1,2" wird eine Summe von Zahlen zurückgegeben, die 3 ist

Test: Für die Eingabe "4,5" wird eine Summe von Zahlen zurückgegeben, die 9 ist

Der zweite Test schlägt fehl.

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]);

(Viel einfacher als eine Liste von if ... return)

In diesem Fall kann man sich sicherlich über die offensichtliche Implementierung streiten, aber wenn Sie davon gesprochen haben, dies ausschließlich in kleinen Schritten zu tun, dann sind dies die richtigen Schritte, IMO.

Das Argument ist, dass, wenn Sie den zweiten Test nicht schreiben, später ein heller Funke auftauchen und Ihren Code zum Lesen "umgestalten" könnte:

return input.Length; # Still satisfies the first test

Und ohne beide Schritte auszuführen, wurde der zweite Test nie rot angezeigt (was bedeutet, dass der Test selbst verdächtig ist).

pdr
quelle
In Bezug auf Ihre Eingabe.Längenbeispiel, mit dem gleichen Erfolg kann ich mir eine verrückte falsche Implementierung vorstellen, die von beiden Tests nicht erfasst wird
SiberianGuy
@Idsa - Ja, absolut, und je mehr Tests Sie schreiben, desto verrückter muss die Implementierung sein. input.Lengthist nicht so weit hergeholt, besonders wenn die Eingabe für die Methode zufällig eine Messung aus einer Datei ist und Sie Ihre Methode absichtlich aufgerufen haben Size().
pdr
6
+1. Dies ist der richtige Weg, um TDD zu lernen. Sobald Sie es gelernt haben, können Sie manchmal direkt zur offensichtlichen Implementierung gehen, aber um ein Gefühl für TDD zu bekommen, ist dies viel besser.
Carl Manaster
1
Ich habe eine Frage zum "Test" selbst. Würden Sie einen neuen Test für die Eingabe "4,5" schreiben oder den ursprünglichen Test ändern?
mxmissile
1
@mxmissile: Ich würde einen neuen Test schreiben. Es nimmt nicht viel Zeit in Anspruch und Sie müssen doppelt so viele Tests durchführen, um sich bei späteren Umgestaltungen zu schützen.
pdr
50

Ich denke, der zweite Weg ist verblüffend dumm. Ich sehe den Wert darin, genügend kleine Schritte zu machen, aber diese winzigen Zygote-Schritte (ich kann sie nicht einmal Baby nennen) zu schreiben, ist nur Unsinn und Zeitverschwendung. Vor allem, wenn das ursprüngliche Problem, das Sie lösen, bereits sehr klein ist.

Ich weiß, es ist Training und es geht mehr darum, das Prinzip zu zeigen, aber ich denke, solche Beispiele tun TDD eher schlecht als gut. Wenn Sie den Wert von Babyschritten zeigen möchten, verwenden Sie zumindest ein Problem, in dem sich ein gewisser Wert befindet.

Christophe Vanfleteren
quelle
+1 und danke, dass ich aufschaue und ein neues Wort lerne (asinine)
Marjan Venema
12
+1 für das Nennen es taub dumm. TDD ist alles schön und so, aber wie bei jeder modernen Hyped-Programmiertechnik sollten Sie darauf achten, sich nicht darin zu verlieren.
Donnerstag,
2
"Vor allem, wenn das ursprüngliche Problem, das Sie lösen, schon sehr klein ist." - Wenn die Eingabe zwei Ints umfassen würde, wäre ich damit einverstanden, aber ich bin nicht überzeugt, wenn es darum geht, "eine Zeichenfolge zu teilen, zwei Ints aus dem Ergebnis zu analysieren und sie hinzuzufügen". Die meisten Methoden in der realen Welt sind nicht viel komplizierter. Tatsächlich sollte es weitere Tests geben, um Randfälle wie das Finden von zwei Kommas, nicht ganzzahligen Werten usw. abzudecken
pdr
4
@pdr: Ich stimme dir zu, dass es mehr Tests geben sollte, um die Randfälle zu behandeln. Wenn Sie sie schreiben und feststellen, dass sich Ihre Implementierung ändern muss, um mit ihnen umzugehen, tun Sie dies auf jeden Fall. Ich schätze, ich habe nur ein Problem damit, Zygote-Schritte zum ersten Happy-Path, der "offensichtlichen Implementierung", zu machen, anstatt das einfach aufzuschreiben und von dort weiterzugehen. Ich sehe keinen Wert darin, eine if-Aussage zu schreiben, von der jede Faser in meinem Körper weiß, dass sie im nächsten Moment verschwinden wird.
Christophe Vanfleteren
1
@ChristopheVanfleteren: Wenn Beck die offensichtliche Implementierung beschreibt, verwendet er die Summe von zwei Ints als Beispiel und warnt immer noch dramatisch davor, wie Sie vor Scham sterben werden, wenn sich Ihr Paar / Rezensent einen einfacheren Code ausdenken kann, der das macht Testdurchlauf. Das ist eine absolute Gewissheit, wenn Sie nur einen Test für dieses Szenario schreiben. Außerdem kann ich mir mindestens drei "offensichtliche" Möglichkeiten vorstellen, um dieses Problem zu lösen: Teilen und Hinzufügen, Ersetzen des Kommas durch + und Bewerten oder Verwenden von Regex. Bei TDD geht es darum, Sie zur richtigen Wahl zu bringen.
pdr
19

Kent Beck behandelt dies in seinem Buch Test Driven Development: By Example.

Ihr Beispiel zeigt eine ' offensichtliche Implementierung ' - Sie möchten die Summe zweier Eingabewerte zurückgeben, und dies ist ein ziemlich grundlegender Algorithmus, der erreicht werden soll. Ihr Gegenbeispiel ist "fälschen, bis Sie es schaffen" (obwohl dies ein sehr einfacher Fall ist).

Offensichtliche Implementierung kann viel komplizierter sein - aber im Grunde beginnt sie, wenn die Spezifikation für eine Methode ziemlich eng ist - zum Beispiel, wenn Sie eine URL-codierte Version einer Klasseneigenschaft zurückgeben - Sie müssen keine Zeit mit ein paar verschwenden gefälschte Kodierungen.

Eine Datenbankverbindungsroutine hingegen würde ein wenig mehr Nachdenken und Testen erfordern, sodass es keine offensichtliche Implementierung gibt (auch wenn Sie möglicherweise bereits mehrmals eine für andere Projekte geschrieben haben).

Von dem Buch:

Wenn ich TDD in der Praxis verwende, wechsle ich häufig zwischen diesen beiden Implementierungsmodi. Wenn alles reibungslos funktioniert und ich weiß, was ich eingeben soll, gebe ich Offensichtliche Implementierung nach Offensichtlicher Implementierung ein (führe die Tests jedes Mal durch, um sicherzustellen, dass mir klar ist, was offensichtlich ist ist für den Computer immer noch offensichtlich). Sobald ich einen unerwarteten roten Balken erhalte, mache ich eine Sicherungskopie, setze falsche Implementierungen ein und überarbeite den richtigen Code. Wenn mein Vertrauen zurückkehrt, gehe ich zurück zu Offensichtliche Implementierungen.

HorusKol
quelle
18

Ich sehe es so, als würde es dem Buchstaben des Gesetzes folgen, aber nicht seinem Geist.

Ihre Babyschritte sollten sein:

So einfach wie möglich, aber nicht einfacher.

Auch das Verb in der Methode ist sum

if (input == "1,2")
   return 3;

ist keine Summe, sondern ein Test für bestimmte Eingaben.

StuperUser
quelle
4

Es erscheint mir in Ordnung, mehrere einfache Umsetzungsschritte zu einem etwas weniger einfachen zusammenzufassen - das mache ich auch die ganze Zeit. Ich denke nicht, dass man religiös werden muss, wenn man TDD jedes Mal genauestens befolgt.

OTOH dies gilt nur für wirklich triviale Schritte wie im obigen Beispiel. Für etwas Komplexeres, das ich nicht sofort im Kopf behalten kann und / oder bei dem ich mir nicht zu 110% sicher bin, gehe ich lieber einen Schritt nach dem anderen.

Péter Török
quelle
1

Wenn Sie sich zum ersten Mal mit TDD befassen, kann die Größe der Stufen ein verwirrendes Problem sein, wie diese Frage veranschaulicht. Eine Frage, die ich mir oft stellte, als ich anfing, testgetriebene Anwendungen zu schreiben, war: Hilft der Test, den ich schreibe, bei der Entwicklung meiner Anwendungen? Dies mag für manche trivial und nicht verwandt erscheinen, aber bleib einen Moment bei mir.

Wenn ich anfange, eine Bewerbung zu schreiben, beginne ich normalerweise mit einem Test. Wie wichtig dieser Test ist, hängt weitgehend von meinem Verständnis dessen ab, was ich zu tun versuche. Wenn ich denke, dass ich so ziemlich das Verhalten einer Klasse im Kopf habe, dann wird der Schritt ein großer sein. Wenn das Problem, das ich zu lösen versuche, viel weniger klar ist, besteht der Schritt möglicherweise einfach darin, dass ich weiß, dass eine Methode mit dem Namen X verwendet wird und Y zurückgegeben wird. Zu diesem Zeitpunkt verfügt die Methode nicht einmal über Parameter und Es besteht die Möglichkeit, dass sich der Name der Methode und der Rückgabetyp ändern. In beiden Fällen treiben die Tests meine Entwicklung voran. Sie erzählen mir Dinge über meine Bewerbung:

Wird diese Klasse, die ich in meinem Kopf habe, tatsächlich funktionieren?

oder

Wie zum Teufel soll ich das überhaupt machen?

Der Punkt ist, dass ich im Handumdrehen zwischen großen und kleinen Schritten wechseln kann. Wenn zum Beispiel ein großer Schritt nicht funktioniert und ich keinen offensichtlichen Weg sehe, wechsle ich zu einem kleineren Schritt. Wenn das nicht funktioniert, wechsle ich zu einem noch kleineren Schritt. Dann gibt es noch andere Techniken wie die Triangulation, wenn ich wirklich stecken bleibe.

Wenn Sie wie ich ein Entwickler und kein Tester sind, müssen Sie mit TDD nicht Tests schreiben, sondern Code schreiben. Lassen Sie sich nicht davon abhalten, viele kleine Tests zu schreiben, wenn diese Ihnen keine Vorteile bringen.

Ich hoffe, Sie haben Ihr Training mit TDD genossen. IMHO, wenn mehr Menschen testinfiziert wären, wäre die Welt ein besserer Ort :)

lexx
quelle
1

In einer Einführung über Unit-Tests las ich den gleichen Ansatz (Schritte, die wirklich sehr, sehr klein aussehen) und als Antwort auf die Frage "Wie klein sollten sie sein?"

Es geht darum, wie sicher Sie sind, dass die Schritte funktionieren. Sie können wirklich große Schritte machen, wenn Sie wollen. Aber versuchen Sie es einfach für einige Zeit und Sie werden eine Menge fehlgeleitetes Vertrauen in Orte finden, die Sie für selbstverständlich halten. Die Tests helfen Ihnen also dabei, ein auf Fakten basierendes Vertrauen aufzubauen.

Also, vielleicht ist dein Kollege nur ein bisschen schüchtern :)

keppla
quelle
1

Ist es nicht der springende Punkt, dass die Implementierung der Methode irrelevant ist, solange die Tests erfolgreich sind? Die Erweiterung der Tests schlägt im zweiten Beispiel schneller fehl, kann jedoch in beiden Fällen zum Fehlschlagen gebracht werden.

Thorsal
quelle
1
Es ist irrelevant, wenn Sie sich nicht darum kümmern, Ihre Zeit zu verschwenden
SiberianGuy
1

Ich bin damit einverstanden, dass die Leute sagen, dass beides nicht die einfachste Implementierung ist.

Die Methodik ist so streng, dass Sie so viele relevante Tests wie möglich schreiben müssen. Es ist in Ordnung, einen konstanten Wert für einen Testfall zurückzugeben und ihn als bestanden zu bezeichnen, da Sie gezwungen sind, zurückzugehen und anzugeben, was Sie wirklich wollen, um etwas anderes als Unsinn aus Ihrem Programm zu holen. Die Verwendung eines solchen trivialen Falls ist in mancher Hinsicht ein Schuss in den Fuß, aber das Prinzip ist, dass sich Fehler in die Lücken in Ihrer Spezifikation einschleichen, wenn Sie versuchen, "zu viel" zu tun und die Anforderung auf die einfachstmögliche Implementierung zu reduzieren, um sicherzustellen, dass a Der Test muss für jeden einzelnen Aspekt des tatsächlich gewünschten Verhaltens geschrieben werden.

Tom W
quelle
Ich habe ein Update bezüglich "Rückgabe eines konstanten Wertes" hinzugefügt
SiberianGuy