Unit Tests, Mock Objekte und Test Driven Development mit PHP
Aus wiki.tommy-schmidt.de
PHP-Entwickler müssen nicht auf mächtige Werkzeuge für Unit-Tests, TDD oder Test-Suites verzichten. Es gibt viele Derivate zu populären Systemen wie JUnit, die nützliche Mechanismen zur Code-Verbesserung und Qualitätssicherung von zu entwickelnder Software auch für PHP mitbringen. Ich möchte hier für meine bevorzugte IDE anhand eines Beispiels den Einsatz eines solchen Werkzeuges vorführen. Grundlage bildet EclipsePDT [1], dessen Installation und Einrichtung ich hier voraussetze. Als Test-Software habe ich mich für SimpleTest for PHP [2] entschieden, welches entweder als eigenständige Komponente oder auch als Eclipse-Plugin verwendet werden kann. Ich möchte beide Varianten kurz anreißen.
Inhaltsverzeichnis |
[bearbeiten] SimpleTest Plugin für Eclipse installieren
Das Eclipse Release der Software kann entweder direkt über die Download-Seite [3] heruntergeladen, oder aber über die Update-Funktion innerhalb von Eclipse importiert werden. Dazu wählt man einfach im Menü "Help" -> "Software Updates" -> "Find and Install" und danach die Option "Search for new features to install". Über "New Remote Site" kann nun die Update-Seite von SimpleTest eingetragen werden.
Die Adresse zur aktuellen "Remote Site" findet man auf der SimpleTest Download-Seite [4]. Mit der Bestätigung des Dialoges und dem Abschließen des Vorgangs kann nun das gefundene Plugin installiert werden.
Um die Installation abzuschließen sind noch ein Neustart von Eclipse und die Konfiguration des Plugins notwendig. Dazu geht man einfach auf "Window" -> "Preferences" und wählt dann den Eintrag "SimpleTest". Im folgenden Formular müssen nun noch einige Werte gesetzt werden:
[bearbeiten] Beispiel-Projekt mit SimpleTest erzeugen
Im nun folgenden Beispiel möchte ich SimpleTest unter Eclipse ohne Plugin-Verwendung vorführen, da sicherlich einige Entwickler andere IDEs verwenden. Als erstes legen wir ein neues PHP-Projekt (z.B. mit dem Namen "TestProject") an, innerhalb dessen wir eine PHP-Datei "File.php" erzeugen. Nun laden wir das aktuelle Release von SimpleTest herunter [5] und entpacken die Archiv-Datei im Projektordner ("TestProject/simpletest/").
Die Datei "File.php" ergänzen wir um folgenden Quellcode:
<?php
class File {
private $filename;
function __construct($pFilename) {
$this->filename = $pFilename;
//if the file does not exist, create it
if (!file_exists($this->filename)) {
$fp = @fopen($this->filename, 'wb');
@fclose($fp);
}
}
public function fileExists() {
return file_exists($this->filename);
}
function fileWrite($pContent) {
$fp = @fopen($this->filename, 'wb');
$tWrite = @fwrite($fp, $pContent);
@fclose($fp);
return ($tWrite === false) ? false : true;
}
function fileAppend($pContent) {
$fp = @fopen($this->filename, 'ab');
$tAppend = @fwrite($fp, $pContent);
@fclose($fp);
return ($tAppend === false) ? false : true;
}
function getContent() {
return @file_get_contents($this->filename);
}
}
?>
Wir haben nun eine Klasse, die zur simplen Erzeugung und Manipulation einer Datei genutzt werden kann. Neben dem Konstruktor, der eine Datei erzeugt, wenn sie nicht bereits vorhanden ist, gibt es noch eine Methode zum Prüfen, ob die Datei existiert ("fileExists"), eine zum Schreiben in die Datei ("fileWrite"), eine zum Anhängen von Inhalt an die Datei ("fileAppend") und eine weitere zum Ausgeben des Dateiinhaltes ("getContent"). Die Klasse ist also recht simpel gehalten und die einzelnen Methoden sollen nun getestet werden. Dazu erzeugen wir eine weitere PHP-Datei "TestFile.php" mit folgendem Quelltext:
<?php
require_once("simpletest/unit_tester.php");
require_once("simpletest/reporter.php");
require_once("File.php");
class TestFile extends UnitTestCase {
private $file;
private $filename;
function __construct($pFilename) {
$this->filename = $pFilename;
$this->file = new file($this->filename);
$this->UnitTestCase('File Manipulation Test');
}
function setUp() {
$fp = @fopen($this->filename, 'x');
if (!($fp === false)) {
fwrite($fp, '');
fclose($fp);
}
}
function tearDown() {
@unlink($this->filename);
}
function testFileExists() {
$this->assertTrue($this->file->fileExists());
}
function testFileWrite() {
$tContent = "test";
$this->assertTrue($this->file->fileWrite($tContent));
}
function testFileAppend() {
$this->file->fileWrite("SimpleTest");
$this->assertTrue($this->file->fileAppend("NeverMind"));
$this->assertWantedPattern('~nevermind$~i', $this->file->getContent());
}
}
$test = new TestFile("test.txt");
$test->run(new HtmlReporter());
?>
Wie wir sehen, werden im oberen Bereich die Dateien "simpletest/unit_tester.php" und "simpletest/reporter.php", sowie "File.php" eingebunden. Die Klasse "TestFile" erbt alle Eigenschaften und Methoden von "UnitTestCase" aus dem SimpleTest-Paket. Wir haben nun erstmal ein wenig Beispiel-Code erzeugt mit dem wir arbeiten können. Zur Erklärung der einzelnen Methoden komme ich im nächsten Abschnitt.
[bearbeiten] Testfälle erzeugen und anwenden
Grundlage beim Testen sind sogenannte Annahmen oder Behauptungen (engl. Asserts). Man behauptet also z.B. das eine Methode einen bestimmten Rückgabewert erzeugen muss. Die Test-Software soll dies nun überprüfen und den Entwickler auf mögliche Probleme hinweisen. Schauen wir uns die Methode "fileExists" aus der Klasse "File" an. Diese prüft lediglich ob eine Datei vorhanden ist. Dazu wird die PHP-Funktion "file_exists" verwendet, die laut Dokumentation einen boolschen Wert, also TRUE oder FALSE zurückgibt. Wir nutzen diesen Rückgabewert und reichen ihn mittels "return" direkt an den Aufrufenden der Methode durch. Nun kommt der spannende Teil. Wir behaupten jetzt also, dass die Methode TRUE zurückgeben muss, wenn die Datei vorhanden ist. Dazu wird in der Klasse "TestFile" folgender Code verwendet:
function testFileExists() {
$this->assertTrue($this->file->fileExists());
}
Mittels "assertTrue" wird auf den Rückgabewert TRUE spekuliert. Führen wir nun die PHP-Datei aus, sollte folgende Ausgabe im Browser Output erscheinen:
Was ist nun eigentlich genau passiert? Mittels "$test = new TestFile("test.txt");" wurde ein neues Objekt aus unserer Test-Klasse erzeugt, in dem die Methode "run" mit "$test->run(new HtmlReporter());" aufgerufen wurde. Diese Methode gehört zu "UnitTestCase", von der unsere Test-Klasse erbt und muss daher nicht neu implementiert werden. Nun werden automatisch weitere Methoden aufgerufen, zum einen "setUp", innerhalb derer wir eine Beispiel-Datei anlegen, mit der wir die einzelnen Tests durchführen und zum anderen "tearDown", wo wir diese Datei wieder entfernen. Vor jedem Testaufruf wird automatisch zuerst "setUp", dann die Test-Methode und gleich danach "tearDown" aufgerufen. Dadurch stellen wir sicher, dass jeder Test die gleichen Ausgangsbedingungen erhält. Unsere Test-Klasse beinhaltet noch zwei weitere Prüfmethoden, "testFileWrite" und "testFileAppend", mit denen wir das Schreiben bzw. Anhängen an eine Datei testen. In letzterer kommt eine neue Behauptung zum Einsatz, "assertWantedPattern". Eine Zeile darüber haben wir einen Eintrag in die Test-Datei vorgenommen und diesen Vorgang gleich noch mit einer Behauptung überprüft:
$this->assertTrue($this->file->fileAppend("SimpleTest"));
Nun wollen wir zusätzlich untersuchen, ob der String "SimpleText" auch tatsächlich im Inhalt der Datei vorkommt. Dazu untersuchen wir diesen mit einem Prüfmuster:
$this->assertWantedPattern('~simpletest$~i', $this->file->getContent());
und werden mit einem positiven Test-Ergebnis belohnt.
Ich empfehle mit den vorgestellten Asserts zu experimentieren und fehlerhafte Ergebnisse zu provozieren um die Funktionsweise besser zu verstehen.
[bearbeiten] Weitere Asserts in SimpleTest
Da Methoden in der Regel nicht nur boolsche Werte zurückgeben, ist es sinnvoll auch noch auf andere Ausgaben zu prüfen. Dazu wird von SimpleTest eine ganze Reihe weiterer Asserts angeboten. Hier ein Auszug:
assertTrue($x) assertFalse($x) assertNull($x) assertNotNull($x) assertIsA($x, $t) assertNotA($x, $t) assertEqual($x, $y) assertNotEqual($x, $y) assertWithinMargin($x, $y, $margin) assertOutsideMargin($x, $y, $margin) assertIdentical($x, $y) assertNotIdentical($x, $y) assertReference($x, $y) assertCopy($x, $y) assertSame($x, $y) assertClone($x, $y) assertPattern($p, $x) assertNoPattern($p, $x) expectError($e) expectException($e)
Die Funktionsweise und eine aktuelle Liste der Asserts findet man auf der SimpleTest-Website [6].
Sobald ich die Zeit finde, werde ich dieses Tutorial noch um Test Driven Development und die Verwendung von Mock-Objekten erweitern und hoffe mit dieser kurzen Anleitung einen guten Einstieg in die Arbeit mit Test-Software geben zu können.




