22 Tools and Utilities
In der Kategorie »Tools and Utilities« sind einige sehr hilfreiche Pakete enthalten, die Sie bei der Entwicklung unterstützen. Die beiden wichtigsten Pakete in dieser Kategorie sind sicher »PHPUnit2« zum automatisierten Testen von Software und »PHPDocumentor«, das Dokumentationen generieren kann.
22.1 PHPUnit2 

Besprochene Version: 2.2.0 | Lizenz: PHP-Lizenz 3.0 |
Klassendatei(en): PHPUnit2/Framework/TestCase.php |
PHPUnit ist ein Paket zum automatisierten Testen von Methoden und Klassen. PHPUnit2 ist der Nachfolger von PHPUnit, das schon lange im Einsatz ist.
PHPUnit2 soll also nicht unbedingt zum Bestandteil Ihrer Klassen werden, sondern es soll Sie nur dabei unterstützen, Ihre Klassen zu testen und eventuell vorhandene Fehler zu finden.
Die ideale Vorgehensweise wäre, dass Sie erst definieren, was Ihre Klasse leisten soll. Das heißt, beim Entwurf der Software definieren Sie, wie eine Methode heißt und was ihre Aufgabe ist bzw. welchen Wert sie zurückgibt. Diese Vorgehensweise hat den großen Vorteil, dass Sie beim Testen nicht auf die Idee kommen, die Tests an Ihre Programmierung anzupassen, um damit »nicht ganz so richtiges« Verhalten zu kaschieren.
Die Schnittstellen und das Verhalten der Methoden vor der Implementation zu definieren hat auch den Vorteil, dass die Entwicklung besser auf verschiedene Programmierer aufgeteilt werden kann.
In diesem Beispiel soll eine Klasse shoppingCart für einen Einkaufswagen in einem Shop implementiert und getestet werden. Die Methoden der Klasse sollen Folgendes leisten:
- mixed addItem($item, $price, $quantity) Fügt dem Einkaufswagen einen Artikel hinzu und bekommt Artikelnummer, Preis und Anzahl übergeben. Der Rückgabewert ist die neue Anzahl der Artikel als Integer-Wert.
- mixed reduceQuantity($item, $quantity) Reduziert die Menge eines Artikels im Warenkorb und bekommt die Artikelnummer und die Anzahl der zu entfernenden Artikel übergeben. Gibt im Erfolgsfall die neue Anzahl der Artikel als Integer-Wert zurück und sonst false.
Für einen produktiven Nutzen fehlen natürlich noch Methoden, aber für das Beispiel soll das reichen.
Haben Sie dieses Verhalten definiert, können Sie beginnen, die Testfälle zu implementieren. Hierbei handelt es sich um Methoden, die prüfen, ob die neu erstellten Methoden sich korrekt verhalten. Um die Testfälle zu implementieren, erweitern Sie die Klasse PHPUnit2_Framework_TestCase. In dieser neuen Klasse werden Methoden implementiert, die die eigentlichen Tests durchführen. Die Namen der Methoden müssen jeweils mit test beginnen und können auf vordefinierte Methoden zugreifen, die prüfen, ob die zu testenden Methoden sich korrekt verhalten.
Diese Methoden, die von der Klasse PHPUnit2_Framework_TestCase vererbt werden, finden Sie in Tabelle 22.1. Diese Methoden werden auch als Zusicherungsmethoden bezeichnet. Dieser Name resultiert daraus, dass die Methode, die getestet werden soll, ein bestimmtes Verhalten zusichert. Die Namen der Zusicherungsmethoden, die dieses Verhalten bestätigen, beginnen jeweils mit assert.
Eine Klasse für einen entsprechenden Test könnte wie in Listing 22.1 aussehen. Ich habe nicht alle Möglichkeiten in diesem Beispiel implementiert, da es sonst sehr umfangreich geworden wäre.
// Einbinden der PEAR-Klasse require_once('PHPUnit2/Framework/TestCase.php'); // Einbinden der zu testenden Klasse require_once('shoppingCart.php'); class tester extends PHPUnit2_Framework_TestCase { public function testInit() { $cart = new shoppingCart(); // Neues Objekt darf noch keinen Inhalt haben $this->assertEquals(0,$cart->getNumItems()); // Keine Artikel, also muss der Preis Null sein $this->assertEquals(0,$cart->getPriceTax()); } public function testAddItem() { $anzahl = 3; $cart = new shoppingCart(); // Artikel hinzufuegen $res = $cart->addItem('1a233d',3.20,$anzahl); // Stimmen Typ und Inhalt der Rueckgabe? $this->assertType ('integer',$res); $this->assertEquals($anzahl,$res); //Korrekte Anzahl von Artikeln im Warenkorb? $this->assertEquals($anzahl,$cart->getNumItems()); } public function testPrice() { $anzahl = 3; $preis = 2.5; $cart = new shoppingCart(); $cart->addItem('abg',$preis,$anzahl); // Preis auslesen $res = $cart->getPriceTax(); $erwartet = sprintf("%.2f",$anzahl*$preis*1.16); // Weichen erwartetes und aktuelles Ergebnis voneinander ab? // Rundungsfehler von 0.01 wird akzeptiert $this->assertEquals($erwartet,$res,null,0.01); } public function testReduceQuantity() { $anzahl = 5; $itemnr= 'gftfr'; $cart = new shoppingCart(); // 5 Artikel hinzufuegen $cart->addItem($itemnr,3.20,$anzahl); // Alle bis auf einen entfernen $res = $cart->reduceQuantity($itemnr,($anzahl-1)); $this->assertEquals(1,$cart->getNumItems()); // Versuchen, noch zwei mehr zu entfernen $cart->reduceQuantity($itemnr,2); // Es muessen jetzt 0 Artikel im Wagen liegen $this->assertEquals(0,$cart->getNumItems()); } }
Listing 22.1 Test-Cases für die Vorgaben (tester.php)
Nachdem die Test-Cases definiert worden sind, fehlt natürlich noch die Klasse, die getestet werden soll. Diese finden Sie in Listing 22.2. Sie ist natürlich nur ein Beispiel und nicht ideal implementiert. Des Weiteren rechnet sie mit einem falschen Mehrwertsteuersatz.
class shoppingCart { // Array zum Verwalten der Daten private $items=array(); // Gibt die Anzahl der Artikel im Korb zurueck public function getNumItems() { if (0 === count ($this->items)) { return 0; } else { $sum = 0; foreach ($this->items as $item) { $sum +=$item['quantity']; } return $sum; } } // Artikel hinzufuegen public function addItem($item, $price, $quantity) { if (true ==isset($this->items[$item])) { $this->items[$item]['quantity']+=$quantity; } else { $this->items[$item]['quantity']=$quantity; $this->items[$item]['price']=$price; } return $this->getNumItems(); } // Anzahl reduzieren public function reduceQuantity($item, $quantity) { if (true ==isset($this->items[$item])) { if ($this->items[$item]['quantity'] > $quantity) { $this->items[$item]['quantity']-=$quantity; } else { unset ($this->items[$item]); } return ($this->getNumItems()); } return false; } // Preis inkl. Steuer auslesen public function getPriceTax() { if (0 === count ($this->items)) { return 0; } else { $sum = 0; foreach ($this->items as $item) { $sum +=$item['quantity']*$item['price']; } return (sprintf("%.2f",$sum*1.15)); } } }
Listing 22.2 Zu testende Klasse (shoppingCart.php)
Um den eigentlichen Test nach der Implementierung durchführen zu können, speichern Sie die Test-Klasse in einer PHP-Datei ab, deren Name der Klasse entspricht. In diesem Fall muss der Dateiname also tester.php lauten.
Der Aufruf des Tests erfolgt auf der Kommandozeile mit dem Befehl phpunit und dem Namen der Datei, die die Test-Klasse enthält.
www> phpunit tester.php PHPUnit 2.2.0 by Sebastian Bergmann. ..F. Time: 0.003942 There was 1 failure: 1) testPrice(tester) expected same: <8.7> was not: <8.63> FAILURES!!! Tests run: 4, Failures: 1, Errors: 0, Incomplete Tests: 0.
Wie Sie sehen können, findet PHPUnit2 den falschen Rückgabewert bei der Berechnung des Gesamtpreises.
Das Paket bietet noch einige zusätzliche Features. Haben Sie schon eine fertige Klassendatei, können Sie das Programm phpunit mit dem Parameter --skeleton und dem Namen der Klassendatei aufrufen. In dem Fall würde eine Vorlage für eine Testdatei erstellt, auf die Sie dann aufbauen können.
Ein anderes Feature, das ganz hilfreich sein kann, ist TestDox. Wenn Sie das Programm mit dem Parameter --testdox-text oder mit dem Parameter --testdox-html aufrufen, wird automatisch eine (kleine) Dokumentation erstellt. Hinter dem Parameter müssen Sie den Namen der Datei angeben, unter dem die Dokumentation erstellt werden soll. Mit dem ersten Parameter erzeugt phpunit eine Text- und mit dem zweiten eine HTML-Datei.