Wie bekomme ich mit TDD die richtige API?

12

Dies könnte eine ziemlich dumme Frage sein, da ich bei meinen ersten TDD-Versuchen bin. Ich mochte das Gefühl des Vertrauens und die allgemein bessere Struktur meines Codes, aber als ich anfing, es auf etwas anzuwenden, das größer war als ein einziges Spielzeugbeispiel, stieß ich auf Schwierigkeiten.

Angenommen, Sie schreiben eine Art Bibliothek. Sie wissen, was es zu tun hat, Sie wissen allgemein, wie es implementiert werden soll (in Bezug auf die Architektur), aber Sie "entdecken" immer wieder, dass Sie während des Codierens Änderungen an Ihrer öffentlichen API vornehmen müssen. Vielleicht müssen Sie diese private Methode in ein Strategiemuster umwandeln (und jetzt müssen Sie in Ihren Tests eine verspottete Strategie bestehen), vielleicht haben Sie hier und da eine Verantwortung verlegt und eine vorhandene Klasse aufgeteilt.

Wenn Sie vorhandenen Code verbessern, scheint TDD wirklich gut zu passen, aber wenn Sie alles von Grund auf neu schreiben, ist die API, für die Sie Tests schreiben, ein bisschen "verschwommen", es sei denn, Sie machen ein großes Design im Vorfeld. Was tun Sie, wenn Sie bereits 30 Tests für die Methode durchgeführt haben, deren Signatur (und für diesen Teil das Verhalten) geändert wurde? Das ist eine Menge Tests, die sich ändern müssen, wenn sie sich summieren.

Vytautas Mackonis
quelle
3
30 Tests auf einer Methode? Klingt so, als wäre diese Methode viel zu komplex, oder Sie schreiben zu viele Tests.
Minthos
Nun, ich habe vielleicht ein bisschen übertrieben, um meinen Standpunkt auszudrücken. Nachdem ich den Code überprüft habe, habe ich im Allgemeinen weniger als 10 Methoden pro Test, wobei die meisten unter 5 liegen.
Vytautas Mackonis
6
@Minthos: Ich kann mir 6 Tests vorstellen, bei denen jede Methode, die eine Zeichenfolge aufnimmt, häufig fehlschlägt oder schlecht abschneidet, wenn der erste Entwurf geschrieben wird (null, leer, zu lang, nicht richtig lokalisiert, schlechte Leistungsskalierung). . Ähnliches gilt für Methoden, die eine Sammlung erstellen. Für eine nicht triviale Methode klingt 30 groß, aber nicht zu unrealistisch.
Steven Evers

Antworten:

13

Was Sie "großes Design im Vorfeld" nennen, nenne ich "vernünftige Planung Ihrer Klassenarchitektur".

Aus Komponententests lässt sich keine Architektur entwickeln. Sogar Onkel Bob sagt das.

Wenn Sie nicht über die Architektur nachdenken, sondern stattdessen die Architektur ignorieren und die Tests zusammenwerfen und zum Bestehen bringen, zerstören Sie das, was dem Gebäude erlaubt, aufrecht zu bleiben, weil es die Konzentration auf das Wesentliche ist Struktur des Systems und solide Entwurfsentscheidungen, die dazu beigetragen haben, dass das System seine strukturelle Integrität beibehält.

http://s3.amazonaws.com/hanselminutes/hanselminutes_0171.pdf , Seite 4

Ich denke, es wäre sinnvoller, sich TDD aus der Perspektive der Validierung Ihres strukturellen Entwurfs zu nähern . Woher wissen Sie, dass das Design falsch ist, wenn Sie es nicht testen? Und wie überprüfen Sie, ob Ihre Änderungen korrekt sind, ohne auch die ursprünglichen Tests zu ändern?

Software ist "soft", weil sie Änderungen unterworfen ist. Wenn Sie sich in Bezug auf das Ausmaß der Änderungen nicht sicher sind, sollten Sie weiterhin Erfahrung mit Architekturdesign sammeln, und die Anzahl der Änderungen, die Sie an Ihren Anwendungsarchitekturen vornehmen müssen, nimmt im Laufe der Zeit ab.

Robert Harvey
quelle
Die Sache ist, auch mit "vernünftiger Planung" erwarten Sie eine Menge davon zu ändern. Normalerweise lasse ich ungefähr 80% meiner ursprünglichen Architektur intakt, mit einigen Änderungen dazwischen. Diese 20% stören mich.
Vytautas Mackonis
2
Ich denke, das liegt in der Natur der Softwareentwicklung. Sie können nicht erwarten, die gesamte Architektur gleich beim ersten Versuch richtig zu machen.
Robert Harvey
2
+1 und es ist nicht gegen TDD. TDD beginnt mit dem Schreiben von Code, genau dann, wenn das Design endet. Mit TDD können Sie feststellen, was Sie in Ihrem Design verpasst haben. So können Sie das Design und die Implementierung überarbeiten und fortfahren.
Steven Evers
2
Laut Bob (und ich stimme ihm voll und ganz zu) ist das Schreiben von Code eigentlich auch Design. Eine Architektur auf hohem Niveau ist auf jeden Fall erforderlich, aber das Design endet nicht, wenn Sie Ihren Code schreiben.
Michael Brown
Wirklich gute Antwort, die den Nagel auf den Kopf trifft. Ich sehe so viele Leute, sowohl für als auch gegen TDD, die scheinbar "kein großes Design im Vorfeld" als "überhaupt kein Design, nur Code" bezeichnen, wenn es sich tatsächlich um einen Streik gegen die verrückten Wasserfall-Designstadien der Vergangenheit handelt. Design ist immer eine gute Zeitinvestition und entscheidend für den Erfolg eines jeden nicht trivialen Projekts.
Sara
3

Wenn Sie TDD machen. Sie können die Signatur und das Verhalten nicht ändern, ohne sie durch Tests gesteuert zu haben. Daher wurden die 30 fehlgeschlagenen Tests entweder gelöscht oder zusammen mit dem Code geändert / umgestaltet. Oder sie sind jetzt veraltet und sicher zu löschen.

Sie können das 30-fache Rot in Ihrem Rot-Grün-Refaktor-Zyklus nicht ignorieren?

Ihre Tests sollten zusammen mit Ihrem Produktionscode überarbeitet werden. Wenn Sie es sich leisten können, führen Sie alle Tests nach jeder Änderung erneut durch.

Haben Sie keine Angst, TDD-Tests zu löschen. Bei einigen Tests werden Bausteine ​​getestet, um das gewünschte Ergebnis zu erzielen. Auf funktionaler Ebene kommt es auf das gewünschte Ergebnis an. Tests zu Zwischenschritten in dem von Ihnen gewählten / erfundenen Algorithmus können von großem Wert sein oder auch nicht, wenn es mehr als einen Weg gibt, um zum Ergebnis zu gelangen, oder Sie anfangs in eine Sackgasse geraten sind.

Manchmal können Sie einige anständige Integrationstests erstellen, diese beibehalten und den Rest löschen. Es hängt ein wenig davon ab, ob Sie von innen nach außen oder von oben nach unten arbeiten und wie viele Schritte Sie unternehmen.

Joppe
quelle
1

Wie Robert Harvey gerade sagte, versuchen Sie wahrscheinlich, TDD für etwas zu verwenden, das von einem anderen konzeptionellen Tool behandelt werden sollte (dh "Design" oder "Modellierung").

Versuchen Sie, Ihr System auf eine recht abstrakte ("allgemeine", "vage") Weise zu entwerfen (oder "modellieren"). Wenn Sie zum Beispiel ein Auto modellieren müssen, müssen Sie nur eine Auto-Klasse mit einer vagen Methode und einem vagen Feld wie startEngine () und int places haben. Das heißt: Beschreiben Sie, was Sie der Öffentlichkeit zugänglich machen möchten, und nicht, wie Sie es implementieren möchten. Versuchen Sie, nur grundlegende Funktionen (Lesen, Schreiben, Starten, Stoppen usw.) verfügbar zu machen, und lassen Sie den Client-Code ausführlich (prepareMyScene (), killTheEnemy () usw.).

Schreiben Sie Ihre Tests unter der Annahme dieser einfachen öffentlichen Schnittstelle auf.

Ändern Sie das interne Verhalten Ihrer Klassen und Methoden nach Bedarf.

Wenn Sie Ihre öffentliche Benutzeroberfläche und Ihre Testsuite ändern müssen, denken Sie nach. Dies ist höchstwahrscheinlich ein Zeichen dafür, dass in Ihrer API und in Ihrem Design / Ihrer Modellierung etwas nicht stimmt.

Es ist nicht ungewöhnlich, eine API zu ändern. Die meisten Systeme in der Version 1.0 warnen die Programmierer / Benutzer ausdrücklich vor möglichen Änderungen in ihrer API. Trotzdem ist ein kontinuierlicher, unkontrollierter Fluss von API-Änderungen ein klares Zeichen für ein schlechtes (oder völlig fehlendes) Design.

Übrigens: Normalerweise sollten Sie nur eine Handvoll Tests pro Methode durchführen. Eine Methode sollte per Definition eine klar definierte "Aktion" für irgendeine Art von Daten implementieren. In einer perfekten Welt sollte dies eine einzelne Aktion sein, die einem einzelnen Test entspricht. In der realen Welt ist es nicht ungewöhnlich (und nicht falsch), wenige unterschiedliche "Versionen" derselben Aktion und wenige unterschiedliche entsprechende Tests zu haben. Sie sollten auf jeden Fall vermeiden, 30 Tests mit derselben Methode durchzuführen. Dies ist ein klares Zeichen dafür, dass die Methode versucht, zu viel zu tun (und der interne Code außer Kontrolle gerät).

AlexBottoni
quelle
0

Ich betrachte es aus der Sicht des Benutzers. Wenn ich beispielsweise mit Ihren APIs ein Person-Objekt mit Name und Alter erstellen kann, sollte es für Name und Alter einen Person-Konstruktor (Zeichenfolgenname, int age) und Zugriffsmethoden geben. Es ist einfach, Testfälle für neue Personen mit und ohne Namen und Alter zu erstellen.

doug

SnoopDougieDoug
quelle