Lassen Sie uns eine einfache App in PHP TDD

In diesem Tutorial werde ich ein End-to-End-Beispiel für eine einfache Anwendung vorstellen, das ausschließlich mit TDD in PHP erstellt wurde. Ich werde Sie Schritt für Schritt durch die einzelnen Schritte führen und gleichzeitig die Entscheidungen erläutern, die ich getroffen habe, um die Aufgabe zu erledigen. Das Beispiel folgt genau den Regeln von TDD: Schreiben von Tests, Schreiben von Code, Refactor.


Schritt 1 - Einführung in TDD & PHPUnit

Testgetriebene Entwicklung (TDD)

TDD ist eine "Test-First" -Technik zum Entwickeln und Entwerfen von Software. Es wird fast immer in agilen Teams eingesetzt und ist eines der zentralen Werkzeuge für die agile Softwareentwicklung. TDD wurde erstmals von Kent Beck im Jahr 2002 definiert und in die Fachwelt eingeführt. Seitdem hat sich TDD zu einer anerkannten und empfohlenen Technik für die tägliche Programmierung entwickelt.

TDD hat drei Kernregeln:

  1. Sie dürfen keinen Produktionscode schreiben, wenn kein fehlerhafter Test vorliegt, um dies zu gewährleisten.
  2. Sie dürfen nicht mehr Komponententests schreiben, als unbedingt erforderlich ist, damit der Test fehlschlägt. Nicht kompilieren / ausführen schlägt fehl.
  3. Sie dürfen nicht mehr Produktionscode schreiben, als für den fehlerhaften Test unbedingt erforderlich ist.

PHPUnit

PHPUnit ist das Tool, mit dem PHP-Programmierer Komponententests durchführen und testgetriebene Entwicklung üben können. Es handelt sich um ein komplettes Unit-Test-Framework mit Mocking-Unterstützung. Obwohl es einige Alternativen gibt, ist PHPUnit heute die am häufigsten verwendete und vollständigste Lösung für PHP.

Um PHPUnit zu installieren, können Sie entweder dem vorherigen Tutorial in unserer "TDD in PHP" -Sitzung folgen oder Sie können PEAR verwenden, wie in der offiziellen Dokumentation beschrieben:

  • werden Wurzel oder verwenden Sudo
  • Stellen Sie sicher, dass Sie die neueste Birne haben: Birne Upgrade Birne
  • Automatische Erkennung aktivieren: pear config-set auto_discover 1
  • PHPUnit installieren: pear install pear.phpunit.de/PHPUnit

Weitere Informationen und Anweisungen zum Installieren zusätzlicher PHPUnit-Module finden Sie in der offiziellen Dokumentation.

Einige Linux-Distributionen bieten an phpunit Als vorkompiliertes Paket empfehle ich jedoch immer eine Installation über PEAR, da dadurch sichergestellt wird, dass die aktuellste und aktuellste Version installiert und verwendet wird.

NetBeans & PHPUnit

Wenn Sie ein Fan von NetBeans sind, können Sie es so konfigurieren, dass es mit PHPUnit zusammenarbeitet, indem Sie die folgenden Schritte ausführen:

  • Wechseln Sie zur Konfiguration von NetBeans (Tools / Optionen).
  • Wählen Sie PHP / Unit Testing aus
  • Prüfen Sie, ob der Eintrag "PHPUnit Script" auf eine gültige PHPUnit-Programmdatei verweist. Wenn dies nicht der Fall ist, wird NetBeans Ihnen dies mitteilen. Wenn Sie also keine roten Hinweise auf der Seite sehen, können Sie dies tun. Wenn nicht, suchen Sie nach der ausführbaren Datei PHPUnit auf Ihrem System und geben Sie den Pfad in das Eingabefeld ein. Bei Linux-Systemen lautet dieser Pfad normalerweise / usr / bin / phpunit.

Wenn Sie keine IDE mit Unterstützung für Komponententests verwenden, können Sie den Test immer direkt von der Konsole aus ausführen:

 cd / mein / applications / test / folder phpunit

Schritt 2 - Das zu lösende Problem

Unser Team ist mit der Implementierung einer "Zeilenumbruch" -Funktion beauftragt.

Nehmen wir an, wir sind Teil eines großen Unternehmens, das eine ausgereifte Anwendung hat, die entwickelt und gewartet werden muss. Unser Team ist mit der Implementierung einer "Zeilenumbruch" -Funktion beauftragt. Unsere Kunden möchten keine horizontalen Bildlaufleisten sehen.

In diesem Fall müssen wir eine Klasse erstellen, die ein beliebiges Textstück formatieren kann, das als Eingabe bereitgestellt wird. Das Ergebnis sollte mit einer bestimmten Anzahl von Zeichen wortweise umbrochen werden. Die Regeln für das Word-Wrapping sollten dem Verhalten anderer Anwendungen des täglichen Lebens folgen, wie z. B. Texteditoren, Textbereichen für Webseiten usw. Unser Kunde versteht nicht alle Regeln für das Word-Wrapping, aber sie wissen, dass sie es wollen, und sie wissen es sollte auf die gleiche Weise funktionieren, wie sie es in anderen Apps erlebt haben.


Schritt 3 - Planung

TDD hilft Ihnen dabei, ein besseres Design zu erreichen, aber Sie müssen nicht erst im Voraus planen und denken.

Viele Programmierer vergessen, nachdem sie TDD gestartet haben, im Voraus zu denken und zu planen. Mit TDD können Sie meistens mit weniger Code und überprüfter Funktionalität ein besseres Design erzielen, erübrigt jedoch nicht die Notwendigkeit eines vorgefertigten Designs und menschlichen Denkens.

Jedes Mal, wenn Sie ein Problem lösen müssen, sollten Sie sich Zeit nehmen, um darüber nachzudenken, sich ein kleines Design vorzustellen - nichts Besonderes -, aber genug, um Sie anzufangen. Dieser Teil des Jobs hilft Ihnen auch, mögliche Szenarien für die Logik der Anwendung vorzustellen und zu erraten.

Lassen Sie uns über die Grundregeln für die Funktion zum Zeilenumbruch nachdenken. Ich nehme an, dass uns etwas unverpackter Text übergeben wird. Wir werden die Anzahl der Zeichen pro Zeile kennen und möchten, dass das Zeichen umbrochen wird. Das erste, was mir in den Sinn kommt, ist, dass, wenn der Text mehr Zeichen enthält als die Nummer in einer Zeile, wir eine neue Zeile anstelle des letzten Leerzeichens hinzufügen sollten, das noch in der Zeile steht.

Okay, das würde das Verhalten des Systems zusammenfassen, aber es ist für jeden Test viel zu kompliziert. Wie sieht es zum Beispiel aus, wenn ein einzelnes Wort länger ist als die Anzahl der Zeichen, die in einer Zeile zulässig sind? Hmmm ... das sieht aus wie ein Randfall; Wir können ein Leerzeichen nicht durch eine neue Zeile ersetzen, da sich in dieser Zeile keine Leerzeichen befinden. Wir sollten zwingen, das Wort einzuwickeln und es effektiv in zwei Teile zu teilen.

Diese Ideen sollten so klar sein, dass wir mit der Programmierung beginnen können. Wir brauchen ein Projekt und eine Klasse. Nennen wir es mal Verpackung.


Schritt 4 - Starten des Projekts und Erstellen des ersten Tests

Lassen Sie uns unser Projekt erstellen. Es sollte einen Hauptordner für Quellklassen geben, und a Tests / Ordner natürlich für die Tests.

Die erste Datei, die wir erstellen werden, ist ein Test innerhalb der Tests Mappe. Alle unsere zukünftigen Tests werden in diesem Ordner enthalten sein, daher werde ich ihn in diesem Tutorial nicht noch einmal explizit angeben. Benennen Sie die Testklasse als beschreibend, aber einfach. WrapperTest wird für jetzt tun; Unser erster Test sieht ungefähr so ​​aus:

 required_once dirname (__ FILE__). '/… /Wrapper.php'; class WrapperTest erweitert PHPUnit_Framework_TestCase function testCanCreateAWrapper () $ wrapper = new Wrapper (); 

Merken! Vor einem fehlgeschlagenen Test dürfen wir keinen Produktionscode schreiben - nicht einmal eine Klassendeklaration! Deshalb habe ich oben den ersten einfachen Test geschrieben canCreateAWrapper. Einige halten diesen Schritt für nutzlos, aber ich halte es für eine gute Gelegenheit, über die Klasse nachzudenken, die wir erstellen werden. Brauchen wir eine Klasse? Wie sollen wir das nennen? Sollte es statisch sein?

Wenn Sie den oben genannten Test ausführen, erhalten Sie eine schwerwiegende Fehlermeldung wie die folgende:

 PHP-Schwerwiegender Fehler: requir_once (): Fehler beim Öffnen von '/ path / to / WordWrapPHP / Tests / ... /Wrapper.php' (include_path = '. / Usr / share / php5: / usr / share / php') in / Pfad / zu / WordWrapPHP / Tests / WrapperTest.php in Zeile 3

Yikes! Wir sollten etwas dagegen unternehmen. Erstellen Sie eine leere Verpackung Klasse im Hauptordner des Projekts.

 Klasse Wrapper 

Das ist es. Wenn Sie den Test erneut ausführen, ist er erfolgreich. Herzlichen Glückwunsch zu Ihrem ersten Test!


Schritt 5 - Der erste echte Test

So haben wir unser Projekt eingerichtet und laufen; Jetzt müssen wir über unser erstes nachdenken echt Prüfung.

Was wäre der einfachste… der dümmste… der grundlegendste Test, der unseren aktuellen Produktionscode zum Scheitern bringen würde? Nun, das erste, was mir einfällt, ist "Geben Sie ein kurzes Wort und erwarten Sie, dass das Ergebnis unverändert bleibt."Das klingt machbar; schreiben wir den Test.

 required_once dirname (__ FILE__). '/… /Wrapper.php'; Klasse WrapperTest erweitert PHPUnit_Framework_TestCase Funktion testDoesNotWrapAShorterThanMaxCharsWord () $ wrapper = new Wrapper (); assertEquals ('word', $ wrapper-> wrap ('word', 5)); 

Das sieht ziemlich kompliziert aus. Was bedeutet "MaxChars" im Funktionsnamen? Was macht 5 in dem wickeln Methode beziehen sich auf?

Ich denke, hier stimmt etwas nicht. Gibt es nicht einen einfacheren Test, den wir ausführen können? Ja, das stimmt! Was ist, wenn wir einwickeln… nichts - eine leere Schnur? Das klingt gut. Löschen Sie den komplizierten Test oben und fügen Sie stattdessen unseren neuen, einfacheren Test hinzu (siehe unten):

 required_once dirname (__ FILE__). '/… /Wrapper.php'; Klasse WrapperTest erweitert PHPUnit_Framework_TestCase Funktion testItShouldWrapAnEmptyString () $ wrapper = new Wrapper (); $ this-> assertEquals (", $ wrapper-> wrap (")); 

Das ist viel besser. Der Name des Tests ist leicht zu verstehen, wir haben keine magischen Zeichenketten oder Zahlen und vor allem IT-Fehler!

 Schwerwiegender Fehler: Aufruf einer undefinierten Methode Wrapper :: wrap () in… 

Wie Sie sehen können, habe ich unseren ersten Test gestrichen. Es ist sinnlos, explizit zu prüfen, ob ein Objekt initialisiert werden kann, wenn andere Tests es auch benötigen. Das ist normal. Mit der Zeit werden Sie feststellen, dass das Löschen von Tests eine übliche Sache ist. Tests, insbesondere Unit-Tests, müssen schnell ablaufen - sehr schnell… und häufig - sehr häufig. Aus diesem Grund ist es wichtig, Redundanz in Tests zu eliminieren. Stellen Sie sich vor, Sie führen jedes Mal Tausende von Tests durch, wenn Sie das Projekt speichern. Es sollte nicht länger als ein paar Minuten dauern, bis sie ausgeführt werden. Haben Sie also keine Angst, einen Test ggf. zu löschen.

Kehren wir zu unserem Produktionscode zurück, machen wir diesen Testdurchlauf:

Klasse Wrapper Funktionsumbruch ($ text) return; 

Oben haben wir absolut nicht mehr Code hinzugefügt, als für den Testdurchlauf erforderlich ist.


Schritt 6 - Drücken Sie Ein

Nun zum nächsten fehlgeschlagenen Test:

 Funktion testItDoesNotWrapAShortEnoughWord () $ wrapper = new Wrapper (); $ this-> assertEquals ('word', $ wrapper-> wrap ('word', 5)); 

Fehlermeldung:

Fehler beim Bestätigen, dass Null mit dem erwarteten "Wort" übereinstimmt.

Und der Code, der es passieren lässt:

 Funktionsumbruch ($ text) return $ text; 

Beeindruckend! Das war einfach, oder??

Wenn wir uns im grünen Bereich befinden, können Sie beobachten, dass unser Testcode anfangen kann, zu faulen. Wir müssen ein paar Dinge umgestalten. Denken Sie daran: immer umgestalten, wenn Ihre Tests bestanden sind; Nur so können Sie sicher sein, dass Sie richtig umgestaltet haben.

Zuerst entfernen wir die Duplizierung der Initialisierung des Wrapper-Objekts. Wir können das nur einmal im tun Konfiguration() Methode und verwenden Sie es für beide Tests.

class WrapperTest erweitert PHPUnit_Framework_TestCase private $ wrapper; function setUp () $ this-> wrapper = new Wrapper ();  function testItShouldWrapAnEmptyString () $ this-> assertEquals (", $ this-> wrapper-> wrap ("));  Funktion testItDoesNotWrapAShortEnoughWord () $ this-> assertEquals ('word', $ this-> wrapper-> wrap ('word', 5)); 

Das Konfiguration Die Methode wird vor jedem neuen Test ausgeführt.

Als nächstes gibt es im zweiten Test einige mehrdeutige Bits. Was ist ein Wort? Was ist '5'? Lassen Sie uns klarstellen, dass der nächste Programmierer, der diese Tests liest, nicht raten muss.

Vergessen Sie nie, dass Ihre Tests auch die aktuellste Dokumentation für Ihren Code sind.

Ein anderer Programmierer sollte in der Lage sein, die Tests so einfach wie die Dokumentation zu lesen.

 Funktion testItDoesNotWrapAShortEnoughWord () $ textToBeParsed = 'word'; $ maxLineLength = 5; $ this-> assertEquals ($ textToBeParsed, $ this-> wrapper-> wrap ($ textToBeParsed, $ maxLineLength)); 

Lesen Sie diese Behauptung jetzt noch einmal. Liest das nicht besser? Natürlich tut es das. Scheuen Sie sich nicht vor langen Variablennamen für Ihre Tests. Autovervollständigung ist dein Freund! Es ist besser so beschreibend wie möglich zu sein.

Nun zum nächsten fehlgeschlagenen Test:

 function testItWrapsAWordLongerThanLineLength () $ textToBeParsed = 'alongword'; $ maxLineLength = 5; $ this-> assertEquals ("Along \ nword", $ this-> wrapper-> wrap ($ textToBeParsed, $ maxLineLength)); 

Und der Code, der es passieren lässt:

 Funktionsumbruch ($ text, $ lineLength) if (strlen ($ text)> $ lineLength) gibt substr ($ text, 0, $ lineLength) zurück. "\ n". substr ($ text, $ lineLength); $ text zurückgeben; 

Das ist der offensichtliche Code, den wir machen müssen zuletzt Testdurchlauf. Aber seien Sie vorsichtig - es ist auch der Code, der unseren ersten Test macht nicht bestanden!

Wir haben zwei Möglichkeiten, um dieses Problem zu beheben:

  • Ändern Sie den Code - machen Sie den zweiten Parameter optional
  • Ändern Sie den ersten Test - und rufen Sie den Code mit einem Parameter auf

Wenn Sie die erste Option auswählen und den Parameter optional machen, würde dies ein kleines Problem mit dem aktuellen Code darstellen. Ein optionaler Parameter wird ebenfalls mit einem Standardwert initialisiert. Was könnte ein solcher Wert sein? Zero mag logisch klingen, aber es würde bedeuten, Code zu schreiben, nur um diesen speziellen Fall zu behandeln. Einstellung einer sehr großen Zahl, damit die erste ob Eine Aussage würde nicht dazu führen, dass true eine andere Lösung ist. Aber wie ist diese Zahl? Ist es 10? Ist es 10000? Ist es 10000000? Wir können es nicht wirklich sagen.

In Anbetracht all dieser Punkte werde ich nur den ersten Test modifizieren:

 function testItShouldWrapAnEmptyString () $ this-> assertEquals (", $ this-> wrapper-> wrap (", 0)); 

Wieder alles grün. Wir können jetzt zum nächsten Test übergehen. Wenn wir ein sehr langes Wort haben, wird es mehrere Zeilen umfassen.

 Funktion testItWrapsAWordSeveralTimesIfItsTooLong () $ textToBeParsed = 'averyverylongword'; $ maxLineLength = 5; $ this-> assertEquals ("avery \ nveryl \ nongwo \ nrd", $ this-> wrapper-> wrap ($ textToBeParsed, $ maxLineLength)); 

Dies schlägt offensichtlich fehl, da unser tatsächlicher Produktionscode nur einmal umläuft.

 Fehler beim Bestätigen, dass zwei Zeichenfolgen gleich sind. --- Erwartet +++ Actual @@ @ '' avery -veryl -ongwo -rd '+ verylongword'

Kannst du das riechen? während Schleife kommt? Nun, denk nochmal nach. Ist ein während schleife den einfachsten Code, der den Test bestehen würde?

Laut 'Transformation Priorities' (von Robert C. Martin) ist dies nicht der Fall. Rekursion ist immer einfacher als eine Schleife und es ist viel testbarer.

 Funktionsumbruch ($ text, $ lineLength) if (strlen ($ text)> $ lineLength) gibt substr ($ text, 0, $ lineLength) zurück. "\ n". $ this-> wrap (substr ($ text, $ lineLength), $ lineLength); $ text zurückgeben; 

Kannst du die Veränderung überhaupt erkennen? Es war einfach. Alles, was wir taten, war, anstatt mit dem Rest der Zeichenfolge verkettet zu werden, verketten wir wir mit dem Rückgabewert des Aufrufens von sich selbst mit dem Rest der Zeichenfolge. Perfekt!


Schritt 7 - Nur zwei Wörter

Der nächst einfachste Test? Was kann man mit zwei Wörtern umgehen, wenn am Ende der Zeile ein Leerzeichen steht?.

 Funktion testItWrapsTwoWordsWhenSpaceAtTheEndOfLine () $ textToBeParsed = 'word word'; $ maxLineLength = 5; $ this-> assertEquals ("word \ nword", $ this-> wrapper-> wrap ($ textToBeParsed, $ maxLineLength)); 

Das passt gut. Die Lösung wird dieses Mal jedoch möglicherweise etwas schwieriger.

Zunächst können Sie sich auf a beziehen str_replace () um den Platz zu entfernen und eine neue Zeile einzufügen. Nicht; Diese Straße führt zu einer Sackgasse.

Die zweite naheliegendste Wahl wäre eine ob Aussage. Etwas wie das:

 Funktionsumbruch ($ text, $ lineLength) if (strpos ($ text, ") == $ lineLength) gibt substr ($ text, 0, strpos ($ text,")) zurück. "\ n". $ this-> wrap (substr ($ text, strpos ($ text, ") + 1), $ lineLength); wenn (strlen ($ text)> $ lineLength) substr ($ text, 0, $ lineLength) zurückgeben." \ n ". $ this-> wrap (substr ($ text, $ lineLength), $ lineLength); $ text zurückgeben;

Dies führt jedoch zu einer Endlosschleife, die dazu führt, dass die Tests fehlschlagen.

Schwerwiegender PHP-Fehler: Die zulässige Speichergröße von 134217728 Byte ist erschöpft

Diesmal müssen wir nachdenken! Das Problem ist, dass unser erster Test einen Text mit einer Länge von Null hat. Ebenfalls, strpos () Gibt false zurück, wenn die Zeichenfolge nicht gefunden werden kann. Falsch mit Null vergleichen ... ist? Es ist wahr. Das ist schlecht für uns, weil die Schleife unendlich wird. Die Lösung? Lassen Sie uns die erste Bedingung ändern. Statt nach einem Leerzeichen zu suchen und seine Position mit der Länge der Linie zu vergleichen, versuchen wir stattdessen, den Charakter direkt an der durch die Länge der Linie angegebenen Position zu nehmen. Wir werden ein tun substr () Nur ein Zeichen lang, beginnend an der richtigen Stelle im Text.

 function wrap ($ text, $ lineLength) if (substr ($ text, $ lineLength - 1, 1) == ") gibt substr ($ text, 0, strpos ($ text,")) zurück. "\ n". $ this-> wrap (substr ($ text, strpos ($ text, ") + 1), $ lineLength); wenn (strlen ($ text)> $ lineLength) substr ($ text, 0, $ lineLength) zurückgeben." \ n ". $ this-> wrap (substr ($ text, $ lineLength), $ lineLength); $ text zurückgeben;

Was aber, wenn das Leerzeichen am Zeilenende nicht stimmt??

 Funktion testItWrapsTwoWordsWhenLineEndIsAfterFirstWord () $ textToBeParsed = 'word word'; $ maxLineLength = 7; $ this-> assertEquals ("word \ nword", $ this-> wrapper-> wrap ($ textToBeParsed, $ maxLineLength)); 

Hmm… wir müssen unsere Bedingungen noch einmal überarbeiten. Ich denke, dass wir diese Suche nach der Position des Raumzeichens brauchen werden.

 Funktionsumbruch ($ text, $ lineLength) if (strlen ($ text)> $ lineLength) if (strpos (substr ($ text, 0, $ lineLength), ")! = 0) gibt substr ($ text, 0) zurück , strpos ($ text, ")). "\ n". $ this-> wrap (substr ($ text, strpos ($ text, ") + 1), $ lineLength); gibt substr ($ text, 0, $ lineLength) zurück." \ n ". $ this-> wrap (substr ($ text, $ lineLength), $ lineLength); $ text zurückgeben;

Beeindruckend! Das funktioniert eigentlich. Wir haben die erste Bedingung in die zweite verschoben, sodass wir die Endlosschleife vermeiden und die Suche nach Platz hinzugefügt haben. Trotzdem sieht es ziemlich hässlich aus. Verschachtelte Bedingungen? Yuck Es ist Zeit für Refactoring.

 Funktionsumbruch ($ text, $ lineLength) if (strlen ($ text) <= $lineLength) return $text; if (strpos(substr($text, 0, $lineLength),") != 0) return substr ($text, 0, strpos($text,")) . "\n" . $this->wrap (substr ($ text, strpos ($ text, ") + 1), $ lineLength); substr ($ text, 0, $ lineLength) zurückgeben." \ n ". $ this-> wrap (substr ($ text, $ lineLength), $ lineLength);

Das ist besser besser.


Schritt 8 - Was ist mit mehreren Wörtern??

Beim Schreiben eines Tests kann nichts Schlimmes passieren.

Der nächst einfachste Test wäre, drei Wörter in drei Zeilen umzuwandeln. Aber dieser Test ist bestanden. Sollten Sie einen Test schreiben, wenn Sie wissen, dass er bestanden wird? Meistens nein. Wenn Sie jedoch Zweifel haben oder sich offensichtliche Änderungen des Codes vorstellen können, die dazu führen, dass der neue Test fehlschlägt und die anderen erfolgreich sind, dann schreiben Sie ihn! Beim Schreiben eines Tests kann nichts Schlimmes passieren. Bedenken Sie auch, dass Ihre Tests Ihre Dokumentation sind. Wenn Ihr Test einen wesentlichen Teil Ihrer Logik darstellt, schreiben Sie ihn!

Die Tatsache, dass die von uns durchgeführten Tests bestanden werden, ist ein Hinweis darauf, dass wir uns einer Lösung nähern. Wenn Sie einen funktionierenden Algorithmus haben, wird jeder Test, den wir schreiben, offensichtlich bestanden.

Nun - drei Wörter in zwei Zeilen, wobei die Zeile im letzten Wort endet; jetzt scheitert das.

 Funktion testItWraps3WordsOn2Lines () $ textToBeParsed = 'Wortwort Wort'; $ maxLineLength = 12; $ this-> assertEquals ("word word \ nword", $ this-> wrapper-> wrap ($ textToBeParsed, $ maxLineLength)); 

Ich hätte fast erwartet, dass dieses funktioniert. Wenn wir den Fehler untersuchen, erhalten wir:

Fehler beim Bestätigen, dass zwei Zeichenfolgen gleich sind. --- Erwartet +++ Tatsächliches @@ @@ -'wortwort -word '+' word + word word '

Ja. Wir sollten uns ganz rechts in eine Zeile einwickeln.

 Funktionsumbruch ($ text, $ lineLength) if (strlen ($ text) <= $lineLength) return $text; if (strpos(substr($text, 0, $lineLength),") != 0) return substr ($text, 0, strrpos($text,")) . "\n" . $this->wrap (substr ($ text, strrpos ($ text, ") + 1), $ lineLength); substr ($ text, 0, $ lineLength) zurückgeben." \ n ". $ this-> wrap (substr ($ text, $ lineLength), $ lineLength);

Einfach das austauschen strpos () mit strrpos () in der zweiten ob Aussage.


Schritt 9 - Andere fehlgeschlagene Tests? Edge-Fälle?

Die Dinge werden schwieriger. Es ist ziemlich schwierig, einen fehlgeschlagenen Test zu finden ... oder einen Test, der noch nicht geschrieben wurde.

Dies ist ein Hinweis darauf, dass wir einer endgültigen Lösung ziemlich nahe sind. Aber ich dachte nur an einen Test, der fehlschlagen wird!

 Funktion testItWraps2WordsOn3Lines () $ textToBeParsed = 'Wort Wort'; $ maxLineLength = 3; $ this-> assertEquals ("wor \ nd \ nwor \ nd", $ this-> wrapper-> wrap ($ textToBeParsed, $ maxLineLength)); 

Aber ich habe mich getäuscht. Es geht vorbei. Hmm ... Sind wir fertig? Warten! Was ist mit diesem hier??

 Funktion testItWraps2WordsAtBoundry () $ textToBeParsed = 'Wort Wort'; $ maxLineLength = 4; $ this-> assertEquals ("word \ nword", $ this-> wrapper-> wrap ($ textToBeParsed, $ maxLineLength)); 

Es schlägt fehl! Ausgezeichnet. Wenn die Zeile die gleiche Länge wie das Wort hat, soll die zweite Zeile nicht mit einem Leerzeichen beginnen.

Fehler beim Bestätigen, dass zwei Zeichenfolgen gleich sind. --- Erwartet +++ Actual @@ @@ 'word -word' + wor + d '

Es gibt verschiedene Lösungen. Wir könnten eine andere vorstellen ob Anweisung zur Überprüfung des Startbereichs. Das würde zu den übrigen Bedingungen passen, die wir erstellt haben. Aber gibt es nicht eine einfachere Lösung? Was ist, wenn wir nur trimmen() der Text?

 Funktionsumbruch ($ text, $ lineLength) $ text = Beschneiden ($ text); if (strlen ($ text) <= $lineLength) return $text; if (strpos(substr($text, 0, $lineLength),") != 0) return substr ($text, 0, strrpos($text,")) . "\n" . $this->wrap (substr ($ text, strrpos ($ text, ") + 1), $ lineLength); substr ($ text, 0, $ lineLength) zurückgeben." \ n ". $ this-> wrap (substr ($ text, $ lineLength), $ lineLength);

Da gehen wir.


Schritt 10 - Wir sind fertig

An diesem Punkt kann ich keinen fehlgeschlagenen Test zum Schreiben erfinden. Wir müssen fertig sein! Wir haben jetzt TDD verwendet, um einen einfachen, aber nützlichen Algorithmus mit sechs Zeilen zu erstellen.

Ein paar Worte zum Stoppen und "getan werden". Wenn Sie TDD verwenden, zwingen Sie sich, über alle möglichen Situationen nachzudenken. Sie schreiben dann Tests für diese Situationen und beginnen damit, das Problem viel besser zu verstehen. Normalerweise führt dieser Prozess zu einer genauen Kenntnis des Algorithmus. Wenn Ihnen beim Schreiben keine weiteren fehlgeschlagenen Tests einfallen, bedeutet das, dass Ihr Algorithmus perfekt ist? Nicht notwendig, es sei denn, es gibt vordefinierte Regeln. TDD garantiert keinen fehlerfreien Code. Es hilft Ihnen nur, besseren Code zu schreiben, der besser verstanden und geändert werden kann.

Besser noch, wenn Sie einen Fehler entdecken, ist es so viel einfacher, einen Test zu schreiben, der den Fehler reproduziert. Auf diese Weise können Sie sicherstellen, dass der Fehler nie wieder auftritt - weil Sie ihn getestet haben!


Abschließende Anmerkungen

Sie können argumentieren, dass dieser Prozess technisch nicht "TDD" ist. Und du hast recht! Dieses Beispiel ist näher an der Anzahl der täglichen Programmierer. Wenn Sie ein echtes Beispiel für "TDD, wie Sie es meinen" wollen, hinterlassen Sie bitte einen Kommentar, und ich werde in Zukunft einen schreiben.

Danke fürs Lesen!