Erstellen Sie die Objekte, von denen Sie denken, dass Sie sie in einem ersten Test in TDD benötigen


15

Ich bin ziemlich neu in TDD und ich habe Probleme, wenn ich meinen ersten Test erstelle, wenn er vor dem Implementierungscode steht. Ohne irgendeinen Rahmen für den Implementierungscode kann ich meinen ersten Test schreiben, wie ich will, aber er scheint immer von meiner Java / OO-Denkweise über das Problem befallen zu sein.

Zum Beispiel habe ich in meinem Github ConwaysGameOfLifeExample den ersten Test geschrieben (rule1_zeroNeighbours), indem ich ein GameOfLife-Objekt erstellt habe, das noch nicht implementiert wurde. bezeichnet eine nicht existierende Set-Methode, eine nicht existierende Step-Methode, eine nicht existierende Get-Methode und verwendet dann eine Zusicherung.

Die Tests entwickelten sich, als ich mehr Tests schrieb und überarbeitete, aber ursprünglich sah es ungefähr so ​​aus:

@Test
public void rule1_zeroNeighbours()
{
    GameOfLife gameOfLife = new GameOfLife();
    gameOfLife.set(1, 1, true);
    gameOfLife.step();
    assertEquals(false, gameOfLife.get(1, 1));
}

Dies fühlte sich seltsam an, als ich das Design der Implementierung erzwang, basierend darauf, wie ich mich zu diesem frühen Zeitpunkt entschlossen hatte, diesen ersten Test zu schreiben.

Wie Sie TDD verstehen, ist das in Ordnung? Ich scheine den TDD / XP-Grundsätzen zu folgen, da sich meine Tests und Implementierung im Laufe der Zeit mit dem Refactoring weiterentwickelt haben. Wenn sich dieses anfängliche Design als unbrauchbar erwiesen hätte, wäre es offen für Änderungen gewesen, aber es scheint, als würde ich eine Richtung auf dem Weg erzwingen Lösung, indem Sie auf diese Weise starten.

Wie nutzen andere Menschen TDD? Ich hätte mehr überarbeiten können, indem ich ohne GameOfLife-Objekt angefangen hätte, nur mit primitiven und statischen Methoden, aber das scheint mir zu kompliziert.


5
TDD ersetzt weder sorgfältige Planung noch sorgfältige Auswahl von Entwurfsmustern. Das heißt, bevor Sie eine Implementierung geschrieben haben, um die ersten Testzeilen zu erfüllen, ist eine weitaus bessere Zeit, als nachdem Sie Abhängigkeiten geschrieben haben, um zu erkennen, dass Ihr Plan dumm ist, dass Sie das falsche Muster gewählt haben oder sogar nur dass es umständlich oder verwirrend ist, eine Klasse so aufzurufen, wie es Ihr Test erfordert.
Svidgen

Antworten:


9

Dies fühlte sich seltsam an, als ich das Design der Implementierung erzwang, basierend darauf, wie ich mich zu diesem frühen Zeitpunkt entschlossen hatte, diesen ersten Test zu schreiben.

Ich denke, das ist der entscheidende Punkt in Ihrer Frage: Ob dies wünschenswert ist oder nicht, hängt davon ab, ob Sie sich an Codeninjas Idee orientieren, dass Sie im Voraus entwerfen und dann TDD verwenden, um die Implementierung auszufüllen, oder an Durrons Idee, dass Tests beteiligt sein sollten das Design sowie die Implementierung zu vertreiben.

Ich denke, welche von diesen bevorzugen Sie (oder wo Sie in die Mitte fallen), ist etwas, das Sie als eine Präferenz für sich entdecken müssen. Es ist nützlich, die Vor- und Nachteile jedes Ansatzes zu verstehen. Es gibt wahrscheinlich viele, aber ich würde sagen, die wichtigsten sind:

Pro Upfront Design

  • Wie gut ein TDD-Prozess auch sein mag, er ist nicht perfekt. Wenn Sie nicht an ein konkretes Ziel denken, kann dies manchmal zu Sackgassen führen, und zumindest einige dieser Sackgassen hätten vermieden werden können, wenn Sie sich vorher überlegt hätten, wo Sie landen möchten. In diesem Blog-Artikel wird dieses Argument am Beispiel der Kata Roman Numerals ausgeführt, und es ist eine recht schöne abschließende Implementierung zu sehen.

Pro Test-Driving Design

  • Wenn Sie Ihre Implementierung auf einem Client Ihres Codes (Ihren Tests) aufbauen, erhalten Sie die YAGNI-Konformität so gut wie kostenlos, solange Sie nicht damit beginnen, nicht benötigte Testfälle zu schreiben. Ganz allgemein erhalten Sie eine API, die auf die Verwendung durch einen Verbraucher zugeschnitten ist und letztendlich Ihren Wünschen entspricht.

  • Die Idee, eine Reihe von UML-Diagrammen zu zeichnen, bevor Code geschrieben wird, und dann nur die Lücken auszufüllen, ist nett, aber selten realistisch. In Steve McConnells Code Complete wird Design bekanntlich als "böses Problem" beschrieben - ein Problem, das Sie nicht vollständig verstehen können, ohne es zuerst zumindest teilweise zu lösen. In Verbindung mit der Tatsache, dass sich das zugrunde liegende Problem durch sich ändernde Anforderungen ändern kann, fühlt sich dieses Designmodell ein bisschen hoffnungslos an. Mit dem Probefahren können Sie immer nur einen Teil der Arbeit abbeißen - nicht nur die Implementierung - und wissen, dass diese Aufgabe zumindest für die Lebensdauer der Umwandlung von Rot in Grün immer noch aktuell und relevant ist.

Was Ihr spezielles Beispiel betrifft, würden Sie, wie Durron sagt, mit einer einfacheren Schnittstelle als der in Ihrem Code-Snippet beginnen, wenn Sie den Entwurf durch Schreiben des einfachsten Tests vertreiben und dabei die minimale Schnittstelle nutzen würden, die es kann .


Der Link war ein sehr gut gelesener Ben. Vielen Dank für das Teilen.
RubberDuck

1
@RubberDuck Gern geschehen! Eigentlich bin ich damit nicht ganz einverstanden, aber ich denke, dass es eine hervorragende Arbeit leistet, diesen Standpunkt zu vertreten.
Ben Aaronson

1
Ich bin mir auch nicht sicher, aber es macht seinen Fall gut. Ich denke, die richtige Antwort liegt irgendwo in der Mitte. Sie müssen einen Plan haben, aber wenn sich Ihre Tests unangenehm anfühlen, müssen Sie sie umgestalten. Wie auch immer ... ++ gute Show alte Bohne.
RubberDuck

17

Um den Test überhaupt schreiben zu können, müssen Sie die API entwerfen, die Sie dann implementieren. Sie haben bereits auf dem falschen Fuß angefangen, indem Sie Ihren Test geschrieben haben, um das gesamte GameOfLife Objekt zu erstellen, und diesen zur Implementierung Ihres Tests verwendet haben.

Aus dem praktischen Unit-Testing mit JUnit und Mockito :

Zuerst könnte es Ihnen unangenehm sein, etwas zu schreiben, das nicht einmal da ist. Es erfordert eine leichte Änderung Ihrer Codierungsgewohnheiten, aber nach einiger Zeit werden Sie feststellen, dass dies eine großartige Gestaltungsmöglichkeit ist. Wenn Sie zuerst Tests schreiben, haben Sie die Möglichkeit, eine API zu erstellen, die für einen Client bequem zu verwenden ist. Ihr Test ist der erste Client einer neugeborenen API. Das ist es, worum es bei TDD wirklich geht: um das Design einer API.

Ihr Test macht keinen großen Versuch, eine API zu entwerfen. Sie haben ein Stateful-System eingerichtet, in dem die gesamte Funktionalität in der äußeren GameOfLifeKlasse enthalten ist.

Wenn ich diese Anwendung schreiben würde, würde ich stattdessen über die Teile nachdenken, die ich bauen möchte. Zum Beispiel könnte ich eine CellKlasse machen, Tests dafür schreiben, bevor ich zur größeren Anwendung übergehe. Ich würde mit Sicherheit eine Klasse für die Datenstruktur "unendlich in jede Richtung" erstellen, die für die ordnungsgemäße Implementierung von Conway erforderlich ist, und das testen. Sobald das alles erledigt ist, würde ich darüber nachdenken, die Gesamtklasse zu schreiben, die eine mainMethode und so weiter hat.

Es ist einfach, den Schritt "Schreiben eines Tests, der fehlschlägt" zu beschönigen. Den fehlerhaften Test so zu schreiben , wie Sie es möchten, ist der Kern von TDD.


1
Wenn man bedenkt, dass eine Zelle nur eine Hülle für eine ist boolean, ist dieses Design mit Sicherheit für die Leistung schlechter. Es sei denn, es muss zukünftig auf andere zellulare Automaten mit mehr als zwei Zuständen erweiterbar sein.
user253751

2
@immibis Das streit über Details. Sie könnten mit einer Klasse beginnen, die die Auflistung von Zellen darstellt. Sie können die Zellenklasse und ihre Tests auch mit einer Klasse migrieren / zusammenführen, die später eine Sammlung von Zellen darstellt, wenn die Leistung ein Problem darstellt.
Eric

@immibis Anzahl der aktiven Nachbarn, die aus Leistungsgründen gespeichert werden können. Anzahl der Zecken, in denen die Zelle aus farblichen Gründen
gelebt hat

@immibis vorzeitige Optimierung ist das Böse ... Darüber hinaus ist die Vermeidung primitiver Besessenheit die beste Möglichkeit, hochwertigen Code zu schreiben, unabhängig davon, wie viele Zustände er unterstützt. Schauen Sie sich Folgendes
Paul

0

Es gibt unterschiedliche Denkrichtungen darüber.

Einige sagen: Nicht kompilierter Test ist ein Fehler - behebe das Problem und schreibe den kleinsten verfügbaren Produktionscode.

Einige sagen: Es ist in Ordnung, zuerst einen Test zu schreiben und dann zu prüfen, ob es scheiße ist (oder nicht) und fehlende Klassen / Methoden zu erstellen

Mit dem ersten Ansatz befinden Sie sich wirklich in einem Rot-Grün-Refaktor-Zyklus. Mit second haben Sie einen etwas breiteren Überblick, was Sie erreichen möchten.

Es liegt an Ihnen zu entscheiden, wie Sie arbeiten. IMHO sind beide Ansätze gültig.


0

Selbst wenn ich etwas auf eine "Hack it together" Weise implementiere, denke ich mir immer noch die Klassen und Schritte aus, die am gesamten Programm beteiligt sein werden. Sie haben dies also durchdacht und diese Design-Gedanken zuerst als Test niedergeschrieben - das ist großartig!

Durchlaufen Sie nun beide Implementierungen, um diesen ersten Test durchzuführen, und fügen Sie dann weitere Tests hinzu, um das Design zu verbessern und zu erweitern.

Was Ihnen helfen könnte, ist die Verwendung von Gurke oder Ähnlichem , um Ihre Tests zu schreiben.


0

Bevor Sie mit dem Schreiben Ihrer Tests beginnen, sollten Sie sich überlegen, wie Sie Ihr System entwerfen. Während der Entwurfsphase sollten Sie einige Zeit aufwenden. Wenn Sie es getan haben, werden Sie diese Verwirrung über TDD nicht bekommen.

TDD ist nur ein Link zum Entwicklungsansatz : TDD
1. Fügen Sie einen Test hinzu.
2. Führen Sie alle Tests aus und prüfen Sie, ob der neue fehlschlägt.
3. Schreiben Sie einen Code.
4. Führen Sie Tests aus.
5. Refactor-Code.
6. Wiederholen

Mit TDD können Sie alle erforderlichen Funktionen abdecken, die Sie geplant haben, bevor Sie mit der Entwicklung Ihrer Software beginnen. link: Vorteile


0

Ich weiß nicht wie System Level Tests geschrieben in Java oder C # aus diesem Grunde. In SpecFlow finden Sie C # oder eines der Cucumber-basierten Test-Frameworks für Java (möglicherweise JBehave). Dann können Ihre Tests eher so aussehen.

Bildbeschreibung hier eingeben

Und Sie können Ihr Objektdesign ändern, ohne alle Ihre Systemtests ändern zu müssen.

("Normale" Komponententests eignen sich hervorragend zum Testen einzelner Klassen.)

Was sind die Unterschiede zwischen BDD-Frameworks für Java?

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.