Advertisement
  1. Code
  2. PHP

Lassen Sie uns eine einfache App in PHP TDD

Scroll to top
Read Time: 18 min
This post is part of a series called Test-Driven PHP.
Automatic Testing for TDD with PHP
Deciphering Testing Jargon

() translation by (you can also view the original English article)

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 ist sie zu einer anerkannten und empfohlenen Technik in der täglichen Programmierung geworden.

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:

  • root werden oder sudo verwenden
  • Stellen Sie sicher, dass Sie die neueste Birne haben: pear upgrade PEAR
  • automatische Erkennung aktivieren: pear config-set auto_discover 1
  • installiere PHPUnit: 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 phpunit als vorkompiliertes Paket an. Ich empfehle jedoch immer eine Installation über PEAR, da so 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:

1
2
cd /my/applications/test/folder
3
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 den Zeilenumbruch sollten dem Verhalten anderer Anwendungen des Alltags folgen, z. B. Texteditoren, Textbereichen für Webseiten usw. Unser Kunde versteht nicht alle Regeln für die Zeilenumbrüche, aber er weiß, dass er es will, und er weiß, dass er auf dieselbe Weise funktionieren sollte, 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 Wrapper.


Schritt 4 - Starten des Projekts und Erstellen des ersten Tests

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

Die erste Datei, die wir erstellen, ist ein Test im Ordner "Tests". 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 vorläufig funktionieren; Unser erster Test sieht ungefähr so ​​aus:

1
2
require_once dirname(__FILE__) . '/../Wrapper.php';
3
4
class WrapperTest extends PHPUnit_Framework_TestCase {
5
6
  function testCanCreateAWrapper() {
7
		$wrapper = new Wrapper();
8
	}
9
10
}

Merken! Vor einem fehlgeschlagenen Test dürfen wir keinen Produktionscode schreiben - nicht einmal eine Klassendeklaration! Aus diesem Grund habe ich oben den ersten einfachen Test namens canCreateAWrapper geschrieben. 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:

1
2
PHP Fatal error:  require_once(): Failed opening required '/path/to/WordWrapPHP/Tests/../Wrapper.php' (include_path='.:/usr/share/php5:/usr/share/php') in /path/to/WordWrapPHP/Tests/WrapperTest.php on line 3

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

1
2
class Wrapper {}

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


Schritt 5 - Der erste echte Test

So haben wir unser Projekt eingerichtet und laufen; Jetzt müssen wir über unseren ersten echten Test nachdenken.

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 "Gib ein kurzes Wort und erwartest, dass das Ergebnis unverändert bleibt." Das klingt machbar; lass uns den Test schreiben.

1
2
require_once dirname(__FILE__) . '/../Wrapper.php';
3
4
class WrapperTest extends PHPUnit_Framework_TestCase {
5
6
	function testDoesNotWrapAShorterThanMaxCharsWord() {
7
		$wrapper = new Wrapper();
8
		assertEquals('word', $wrapper->wrap('word', 5));
9
	}
10
11
}

Das sieht ziemlich kompliziert aus. Was bedeutet "MaxChars" im Funktionsnamen? Worauf bezieht sich 5 in der Wrap-Methode?

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):

1
2
require_once dirname(__FILE__) . '/../Wrapper.php';
3
4
class WrapperTest extends PHPUnit_Framework_TestCase {
5
6
	function testItShouldWrapAnEmptyString() {
7
		$wrapper = new Wrapper();
8
		$this->assertEquals('', $wrapper->wrap(''));
9
	}
10
11
}

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

1
2
Fatal error: Call to undefined method 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:

1
class Wrapper {
2
3
	function wrap($text) {
4
		return;
5
	}
6
7
}

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:

1
	function testItDoesNotWrapAShortEnoughWord() {
2
		$wrapper = new Wrapper();
3
		$this->assertEquals('word', $wrapper->wrap('word', 5));
4
	}

Fehlermeldung:

1
Failed asserting that null matches expected 'word'.

Und der Code, der es passieren lässt:

1
	function wrap($text) {
2
		return $text;
3
	}

Wow! Das war einfach, nicht wahr?

Beachten Sie, dass unser Testcode anfangen kann, zu faulen, während wir im grünen Bereich sind. 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 dies nur einmal in der Methode setUp () durchführen und für beide Tests verwenden.

1
class WrapperTest extends PHPUnit_Framework_TestCase {
2
3
	private $wrapper;
4
5
	function setUp() {
6
		$this->wrapper = new Wrapper();
7
	}
8
9
	function testItShouldWrapAnEmptyString() {
10
		$this->assertEquals('', $this->wrapper->wrap(''));
11
	}
12
13
	function testItDoesNotWrapAShortEnoughWord() {
14
		$this->assertEquals('word', $this->wrapper->wrap('word', 5));
15
	}
16
17
}

Die Setup-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.

1
	function testItDoesNotWrapAShortEnoughWord() {
2
		$textToBeParsed = 'word';
3
		$maxLineLength = 5;
4
		$this->assertEquals($textToBeParsed, $this->wrapper->wrap($textToBeParsed, $maxLineLength));
5
	}

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:

1
	function testItWrapsAWordLongerThanLineLength() {
2
		$textToBeParsed = 'alongword';
3
		$maxLineLength = 5;
4
		$this->assertEquals("along\nword", $this->wrapper->wrap($textToBeParsed, $maxLineLength));
5
	}

Und der Code, der es passieren lässt:

1
	function wrap($text, $lineLength) {
2
		if (strlen($text) > $lineLength)
3
			return substr ($text, 0, $lineLength) . "\n" . substr ($text, $lineLength);
4
		return $text;
5
	}

Das ist der offensichtliche Code für unseren letzten Testdurchlauf. Aber seien Sie vorsichtig - es ist auch der Code, der unseren ersten Test nicht bestehen lässt!

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. Das Festlegen einer sehr großen Zahl, sodass die erste if -Anweisung nicht zu true führen könnte, kann eine andere Lösung sein. 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:

1
	function testItShouldWrapAnEmptyString() {
2
		$this->assertEquals('', $this->wrapper->wrap('', 0));
3
	}

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.

1
	function testItWrapsAWordSeveralTimesIfItsTooLong() {
2
		$textToBeParsed = 'averyverylongword';
3
		$maxLineLength = 5;
4
		$this->assertEquals("avery\nveryl\nongwo\nrd", $this->wrapper->wrap($textToBeParsed, $maxLineLength));
5
	}

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

1
2
Failed asserting that two strings are equal.
3
--- Expected
4
+++ Actual
5
@@ @@
6
 'avery

7
-veryl

8
-ongwo

9
-rd'
10
+verylongword'

Kannst du die while-Schleife riechen? Nun, denk nochmal nach. Ist eine while-Schleife der einfachste 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 ist viel testbarer.

1
	function wrap($text, $lineLength) {
2
		if (strlen($text) > $lineLength)
3
			return substr ($text, 0, $lineLength) . "\n" . $this->wrap(substr($text, $lineLength), $lineLength);
4
		return $text;
5
	}

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?

1
	function testItWrapsTwoWordsWhenSpaceAtTheEndOfLine() {
2
		$textToBeParsed = 'word word';
3
		$maxLineLength = 5;
4
		$this->assertEquals("word\nword", $this->wrapper->wrap($textToBeParsed, $maxLineLength));
5
	}

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

Zuerst können Sie sich auf ein str_replace () beziehen, um das Leerzeichen zu löschen und eine neue Zeile einzufügen. Nicht; Diese Straße führt zu einer Sackgasse.

Die zweithäufigste Wahl wäre eine if-Anweisung. Etwas wie das:

1
	function wrap($text, $lineLength) {
2
		if (strpos($text,' ') == $lineLength)
3
			return substr ($text, 0, strpos($text, ' ')) . "\n" . $this->wrap(substr($text, strpos($text, ' ') + 1), $lineLength);
4
		if (strlen($text) > $lineLength)
5
			return substr ($text, 0, $lineLength) . "\n" . $this->wrap(substr($text, $lineLength), $lineLength);
6
		return $text;
7
	}

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

1
PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted

Diesmal müssen wir nachdenken! Das Problem ist, dass unser erster Test einen Text mit einer Länge von Null hat. Strpos () gibt auch false zurück, wenn der String nicht gefunden wird. Falsch mit Null vergleichen ... ist? Es ist true. 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 substr () nur ein Zeichen lang machen, beginnend an der richtigen Stelle im Text.

1
	function wrap($text, $lineLength) {
2
		if (substr($text, $lineLength - 1, 1) == ' ')
3
			return substr ($text, 0, strpos($text, ' ')) . "\n" . $this->wrap(substr($text, strpos($text, ' ') + 1), $lineLength);
4
		if (strlen($text) > $lineLength)
5
			return substr ($text, 0, $lineLength) . "\n" . $this->wrap(substr($text, $lineLength), $lineLength);
6
		return $text;
7
	}

Aber was ist, wenn der Platz nicht am Ende der Zeile liegt?

1
	function testItWrapsTwoWordsWhenLineEndIsAfterFirstWord() {
2
		$textToBeParsed = 'word word';
3
		$maxLineLength = 7;
4
		$this->assertEquals("word\nword", $this->wrapper->wrap($textToBeParsed, $maxLineLength));
5
	}

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

1
	function wrap($text, $lineLength) {
2
		if (strlen($text) > $lineLength) {
3
			if (strpos(substr($text, 0, $lineLength), ' ') != 0)
4
				return substr ($text, 0, strpos($text, ' ')) . "\n" . $this->wrap(substr($text, strpos($text, ' ') + 1), $lineLength);
5
			return substr ($text, 0, $lineLength) . "\n" . $this->wrap(substr($text, $lineLength), $lineLength);
6
		}
7
		return $text;
8
	}

Wow! 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.

1
	function wrap($text, $lineLength) {
2
		if (strlen($text) <= $lineLength)
3
			return $text;
4
		if (strpos(substr($text, 0, $lineLength), ' ') != 0)
5
			return substr ($text, 0, strpos($text, ' ')) . "\n" . $this->wrap(substr($text, strpos($text, ' ') + 1), $lineLength);
6
		return substr ($text, 0, $lineLength) . "\n" . $this->wrap(substr($text, $lineLength), $lineLength);
7
	}

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, 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.

1
	function testItWraps3WordsOn2Lines() {
2
		$textToBeParsed = 'word word word';
3
		$maxLineLength = 12;
4
		$this->assertEquals("word word\nword", $this->wrapper->wrap($textToBeParsed, $maxLineLength));
5
	}

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

1
Failed asserting that two strings are equal.
2
--- Expected
3
+++ Actual
4
@@ @@
5
-'word word

6
-word'
7
+'word

8
+word word'

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

1
	function wrap($text, $lineLength) {
2
		if (strlen($text) <= $lineLength)
3
			return $text;
4
		if (strpos(substr($text, 0, $lineLength), ' ') != 0)
5
			return substr ($text, 0, strrpos($text, ' ')) . "\n" . $this->wrap(substr($text, strrpos($text, ' ') + 1), $lineLength);
6
		return substr ($text, 0, $lineLength) . "\n" . $this->wrap(substr($text, $lineLength), $lineLength);
7
	}

Ersetzen Sie einfach strpos () durch strrpos () in der zweiten if-Anweisung.


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 scheitern wird!

1
	function testItWraps2WordsOn3Lines() {
2
		$textToBeParsed = 'word word';
3
		$maxLineLength = 3;
4
		$this->assertEquals("wor\nd\nwor\nd", $this->wrapper->wrap($textToBeParsed, $maxLineLength));
5
	}

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

1
	function testItWraps2WordsAtBoundry() {
2
		$textToBeParsed = 'word word';
3
		$maxLineLength = 4;
4
		$this->assertEquals("word\nword", $this->wrapper->wrap($textToBeParsed, $maxLineLength));
5
	}

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.

1
Failed asserting that two strings are equal.
2
--- Expected
3
+++ Actual
4
@@ @@
5
 'word

6
-word'
7
+ wor
8
+d'

Es gibt verschiedene Lösungen. Wir könnten eine andere if-Anweisung einführen, um nach dem Startraum zu suchen. Das würde zu den übrigen Bedingungen passen, die wir erstellt haben. Aber gibt es nicht eine einfachere Lösung? Was ist, wenn wir den Text gerade trim()?

1
	function wrap($text, $lineLength) {
2
		$text = trim($text);
3
		if (strlen($text) <= $lineLength)
4
			return $text;
5
		if (strpos(substr($text, 0, $lineLength), ' ') != 0)
6
			return substr ($text, 0, strrpos($text, ' ')) . "\n" . $this->wrap(substr($text, strrpos($text, ' ') + 1), $lineLength);
7
		return substr ($text, 0, $lineLength) . "\n" . $this->wrap(substr($text, $lineLength), $lineLength);
8
	}

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 Kommentar schreiben.

Danke fürs Lesen!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.