Copyright © 2005, 2006, 2007, 2008, 2009 Sebastian Bergmann
Ausgabe für PHPUnit 2.3. Aktualisiert am 29.12.2009.
Lange war meine Antwort auf die Frage nach Dokumentation für PHPUnit, dass man eigentlich gar keine Dokumentation für PHPUnit brauche. Stattdessen solle man die Dokumentation von JUnit lesen und sich die Code-Beispiele von Java und JUnit in PHP und PHPUnit "übersetzen". Als ich dies Barbara Weiss und Alexandra Follenius von O'Reilly Deutschland gegenüber erwähnte, überzeugten sie mich, diese Einstellung zu überdenken. Sie ermutigten mich, ein Buch über PHPUnit zu schreiben, das als Dokumentation dienen solle.
Das Thema dieses Buchs ist PHPUnit, das Open Source-Tool der Wahl für die testgetriebene Entwicklung in PHP. Diese Auflage behandelt Version 2.3 von PHPUnit. Die meisten Code-Beispiele sollten allerdings auch mit den Versionen 2.0 bis 2.2 von PHPUnit funktionieren. Anhang A behandelt die alte, nicht mehr aktiv weiterentwickelte Version von PHPUnit für PHP 4.
Der Leser sollte objektorientierte Programmierung mit PHP 5 bereits verstanden haben. Eine gute Einführung in dieses Thema bietet mein Buch "Professionelle Softwareentwicklung mit PHP 5" [Bergmann2005].
Dieses Buch ist unter der Creative Commons-Namensnennung-Lizenz verfügbar
(siehe Anhang D). Die aktuelle Version
finden Sie stets unter http://www.phpunit.de/pocket_guide/. Sie
können das Buch verteilen und auch nach Belieben Änderungen daran
vornehmen. Jedoch wünsche ich mir, dass Sie mir, anstatt Ihre eigene
private Version des Buchs zu vertreiben, Feedback und Änderungen per
E-Mail an <sb@sebastian-bergmann.de> zukommen lassen, so
dass ich sie in das Buch einpflegen kann.
kennzeichnet Datei- und Verzeichnisnamen, E- Mail-Adressen, URLs, Hervorhebungen und neu eingeführte Begriffe.
Nichtproportionalschriftkennzeichnet Codebeispiele, Variablen, Funktionen und Befehlsoptionen.
Nichtproportionalschrift kursivkennzeichnet Platzhalter für Elemente, die durch aktuelle Werte in Ihrem eigenen Programm ersetzt werden müssen.
Nichtproportionalschrift fettkennzeichnet Benutzereingaben in Beispielen und wird außerdem in Codebeispielen zur Hevorhebung verwendet.
Dies ist ein Tipp oder ein genereller Hinweis mit nützlichen Zusatzinformationen zum Thema.
Ich möchte mich bei Kent Beck und Erich Gamma für JUnit und die Inspiration, PHPUnit zu entwickeln, bedanken. Ich möchte mich bei Kent Beck für sein Buch "JUnit Pocket Guide" [Beck2004] bedanken, das die Idee für dieses Buch lieferte. Außerdem möchte ich mich bei Allison Randal, Alexandra Follenius und Barbara Weiss bedanken, die dieses Buch bei O'Reilly gefördert und begleitet haben.
Für ihre Arbeit an der Zend Engine 2, dem Kern von PHP 5, danke ich Andi Gutmans, Zeev Suraski sowie Marcus Börger. Ohne sie gäbe es weder PHP 5 und PHPUnit noch dieses Buch. Für seine Xdebug-Erweiterung für PHP danke ich Derick Rethans. Ohne diese Erweiterung wäre die die Code-Coverage-Analyse von PHPUnit nicht möglich. Für seine Integration von PHPUnit in Phing danke ich Michiel Rook.
Selbst gute Programmierer machen Fehler. Der Unterschied zwischen einem guten Programmierer und einem schlechten Programmierer besteht darin, dass der gute Programmierer Tests benutzt, um festzustellen, ob er einen Fehler gemacht hat. Je früher Sie dies testen, desto größer ist die Chance, den Fehler zu finden, und desto geringer sind die Kosten seiner Behebung. Dies erklärt, warum es so teuer und fehlerträchtig ist, die Tests auf die Zeit unmittelbar vor der Veröffentlichung der Software zu verschieben. Die meisten Fehler werden zu dieser Zeit überhaupt nicht mehr gefunden. Dazu sind die Kosten für das Auffinden und Beseitigen der dann noch erkannten Fehler so hoch, dass man es sich kaum leisten kann, alle zu beheben.
Testen mit PHPUnit unterscheidet sich nicht wesentlich von dem, was Sie bereits tun sollten, es ist lediglich eine andere Methode. Der Unterschied besteht darin, dass Sie nicht einfach nur testen, sondern eine Reihe von Tests anwenden, nämlich Codefragmente, die eine Anwendung automatisch überprüfen und sicherstellen, dass sie sich den Erwartungen entsprechend verhält. Diese Codefragmente nennt man auch Unit Tests.
In diesem Kapitel werden wir uns damit beschäftigen, wie Sie das
print-basierte Testen durch vollautomatisierte
Tests ersetzen können. Stellen Sie sich vor, Sie wollen die von
PHP bereitgestellte Datenstruktur array sowie die
Funktion sizeof() testen. Für ein neu erzeugtes
array muss sizeof() den Wert
0 liefern, für ein array mit
einem Element muss sizeof() den Wert
1 liefern.
Beispiel 1.1 zeigt,
was wir testen wollen.
Beispiel 1.1: Testen von Array und sizeof()
<?php
$fixture = array();
// Wir erwarten, dass $fixture leer ist.
$fixture[] = 'element';
// Wir erwarten, dass $fixture ein Element enthält.
?>
Ob Sie die erwarteten Ergebnisse erhalten, können Sie auf sehr
einfache Weise überprüfen, indem Sie den Rückgabewert von
sizeof() vor und nach dem Hinzufügen eines
Elementes ausgeben lassen
(Beispiel 1.2).
array und sizeof() verhalten
sich erwartungskonform, wenn dabei erst 0 und dann
1 erscheint.
Beispiel 1.2: Testen von Array und sizeof() mit print
<?php
$fixture = array();
print sizeof($fixture) . "\n";
$fixture[] = "element";
print sizeof($fixture) . "\n";
?>
0 1
Nun wollen wir von einem Test, der eine manuelle Interpretation
erfordert, zu einem automatisch ablaufenden Test übergehen. In
Beispiel 1.3 werden
die berechneten mit den von uns erwarteten Werten verglichen. Ausgegeben
wird ok oder nicht ok, je nachdem,
ob der Test erfolgreich war oder nicht. Dabei brauchen Sie beim
Betrachten der Ausgabe nur darauf zu achten, dass in allen Zeilen
ok steht. Sobald Sie nicht ok
sehen, wissen Sie, dass irgendetwas schief gegangen ist.
Beispiel 1.3: Vergleichen von berechneten und erwarteten Werten
<?php
$fixture = array();
print sizeof($fixture) == 0 ? "ok\n" : "nicht ok\n";
$fixture[] = 'element';
print sizeof($fixture) == 1 ? "ok\n" : "nicht ok\n";
?>
ok ok
Ziehen Sie nun den Vergleich von erwarteten und berechneten Werten aus
den Tests heraus in eine Funktion, die einen Booleschen Wert als Eingabe
annimmt und nichts weiter tut, als eine Ausnahme auszulösen, wenn dieser
Wert FALSE ist
(Beispiel 1.4).
Jetzt sieht die Ausgabe schon viel übersichtlicher aus. Solange der Test
erfolgreich durchläuft, wird gar nichts angezeigt. An dem Erscheinen einer
unbehandelten Ausnahme erkennen Sie, dass etwas schief gegangen ist.
Beispiel 1.4: Testen von Array und sizeof() mit einer Zusicherung
<?php
$fixture = array();
assertTrue(sizeof($fixture) == 0);
$fixture[] = 'element';
assertTrue(sizeof($fixture) == 1);
function assertTrue($condition)
{
if (!$condition) {
throw new Exception('Zusicherung fehlgeschlagen.');
}
}
?>
Der Test ist nun vollständig automatisiert. Anstatt lediglich zu testen und das Ergebnis dann manuell zu interpretieren, wie wir es in der ersten Fassung des Tests getan haben, stellt diese Fassung einen automatisierten Test dar, der nicht mehr manuell ausgewertet werden muss.
Man wird nie alle Mängel in einem Programm erkennen können. Häufigeres Testen reduziert aber die Zahl der Fehler, die zum Schluss noch übrig bleiben. Das Ziel der automatisierten Tests ist ein begründetes Vertrauen in den von Ihnen geschriebenen Code. Diesen Vertrauensgewinn können Sie nutzen, um Ihre Designs innovativer zu gestalten (Refactoring), besser mit Ihren Team-Kollegen auszukommen (Teamübergreifende Tests), die Beziehungen zu Ihren Kunden zu verbessern -- und jeden Tag mit der Gewissheit nach Hause zu gehen, dass das System jetzt aufgrund Ihrer Bemühungen besser läuft als noch am Morgen.
Bislang haben wir erst zwei Tests für den Datentyp array
und die Funktion sizeof(). Wenn wir die Vielzahl an
array_*() Funktionen, die PHP anbietet, testen wollen,
so müssen wir für jede dieser Funktionen mindestens einen Test schreiben.
Für alle diese Tests könnten wir uns eine von Grund auf neue Infrastruktur
aufbauen. Allerdings wünschen wir uns eine wiederverwendbare Infrastruktur,
für die wir nur noch die für jeden Test spezifischen Teile schreiben
müssen. Genau solch eine Infrastruktur ist PHPUnit.
Beispiel 2.1 zeigt, wie die beiden Tests aus Beispiel 1.4 zu formulieren sind, damit wir sie mit PHPUnit verwenden können.
Beispiel 2.1: Testen von Array und sizeof() mit PHPUnit
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
class ArrayTest extends PHPUnit2_Framework_TestCase {
public function testNewArrayIsEmpty() {
// Array-Fixture erzeugen.
$fixture = array();
// Der erwartete Wert von sizeof($fixture) ist 0.
$this->assertEquals(0, sizeof($fixture));
}
public function testArrayContainsAnElement() {
// Array-Fixture erzeugen.
$fixture = array();
// Ein Element dem Array hinzufügen.
$fixture[] = 'Element';
// Der erwartete Wert von sizeof($fixture) ist 1.
$this->assertEquals(1, sizeof($fixture));
}
}
?>
Beispiel 2.1 zeigt die grundlegenden Schritte für das Schreiben von PHPUnit-Tests:
Die Tests für eine Klasse Class werden in einer Klasse ClassTest geschrieben.
ClassTest erbt (in den meisten Fällen) von PHPUnit2_Framework_TestCase.
Die Tests sind öffentliche Methoden, die keine Parameter erwarten und test* heißen.
Innerhalb der Testmethoden werden Zusicherungsmethoden wie assertEquals() (siehe Tabelle 14.1) verwendet, um tatsächliche Werte mit erwarteten Werten zu vergleichen.
Jedes Framework muss eine Reihe von Anforderungen erfüllen, von denen immer einige miteinander in Konflikt zu stehen scheinen. PHPUnit ist da keine Ausnahme, denn Tests sollen all dies zugleich sein:
Die Zielgruppe für PHPUnit sind Programmierer und nicht professionelle Tester, daher müssen die Barrieren für das Schreiben von Tests minimal sein.
Tests, die nicht leicht zu schreiben sind, werden die Programmierer nicht schreiben.
Der Testcode sollte keinen Overhead enthalten, der vom Kern des Tests ablenkt.
Die Tests sollten auf einen Button-Klick hin starten und ihre Ergebnisse in einem klaren und eindeutigen Format präsentieren.
Die Tests sollten so schnell ablaufen, dass wir sie hundert- oder tausendmal am Tag ausführen können.
Die Tests sollen sich nicht gegenseitig beeinflussen. Auch wenn sich die Reihenfolge der Testläufe verändert, sollten die Ergebnisse gleich bleiben.
Jede beliebige Anzahl und Folge von Tests sollten zusammen ausgeführt werden können. Dies ist eine logische Konsequenz aus der Isolation.
Zwischen diesen Bedingungen gibt es zwei wesentliche Kollisionen:
Im Allgemeinen erfordern Tests nicht die gesamte Flexibilität einer Programmiersprache, insbesondere nicht die einer objektorientierten Sprache. Viele Testwerkzeuge bieten eigene Skriptsprachen, die nur das Wenige an Möglichkeiten umfassen, das für das Schreiben von Tests erforderlich ist. Dann sind die Tests leicht zu lesen und zu schreiben, denn sie enthalten nichts, was von dem Inhalt des Tests ablenkt. Andererseits ist das Erlernen einer weiteren Programmiersprache und der zugehörigen Programmierwerkzeuge unbequem und verwirrend.
Wenn die Ergebnisse eines Tests keine Auswirkungen auf die Ergebnisse eines anderen Tests haben sollen, muss jeder Test vor dem Beginn seiner Ausführung den Zustand der Welt komplett herstellen und sie nach seiner Beendigung wieder in ihren Ursprungszustand zurückversetzen. Dieses Aufbauen der Welt kann allerdings eine beträchtliche Zeit in Anspruch nehmen, zum Beispiel wenn die Verbindung zu einer Datenbank hergestellt und die Datenbank mittels realistischer Daten in einen definierten Zustand gebracht werden muss.
PHPUnit löst diese Konflikte, in dem es zunächst mal das uns vertraute PHP als Testsprache verwendet. Die Möglichkeiten dieser Sprache dürften manchmal des Guten zuviel sein, wenn es um das Schreiben kleiner, gradliniger Tests geht. Aber indem wir PHP verwenden, können wir die Erfahrung und die Werkzeuge nutzen, die den Programmierern ohnehin zur Verfügung stehen. Wenn wir versuchen wollen, skeptische Tester zu überzeugen, ist das Absenken der Schwelle zum Schreiben jener ersten Tests besonders wichtig.
Außerdem gewichtet PHPUnit die Isolation stärker als die Ausführungsgeschwindigkeit. Isolierte Tests sind wichtig, weil sie uns ein Feedback von hoher Qualität liefern. Es gibt keine Reports mit reihenweise gescheiterten Tests, die in Wirklichkeit nur deswegen fehlgeschlagen sind, weil ein Test am Anfang der Testreihe gescheitert ist und für die übrigen Tests alles durcheinander gebracht hat. Dieser Fokus auf isolierte Tests ermutigt Entwürfe mit einer großen Zahl einfach gehaltener Objekte. In der Isolierung kann jedes Objekt schnell getestet werden. Das Ergebnis sind also bessere Entwürfe und schnellere Tests.
Als weitere systematische Besonderheit geht PHPUnit davon aus, dass Tests im Normalfall erfolgreich durchlaufen. Nur wenn ein Test fehlschlägt, muss dies festgehalten und gemeldet werden. Die große Mehrzahl der Tests sollte aber erfolgreich sein und außer der jeweiligen Anzahl von Durchläufen keiner weiteren Kommentare bedürfen. Tatsächlich ist diese Annahme in die Auswertungsklassen eingebaut und nicht in das PHPUnit-Kernsystem. Wenn die Ergebnisse eines Testlaufs gemeldet werden, sehen Sie die Anzahl der insgesamt ausgeführten Tests, Details werden aber nur zu den fehlgeschlagenen Tests gemeldet.
Von den Tests wird erwartet, dass sie feinkörnig sind und jeweils nur einen Aspekt eines Objekts prüfen. Dementsprechend brechen Tests ab, sobald sie zum ersten Mal fehlschlagen, und PHPUnit meldet dies. Das Aufgliedern in viele kleine Tests ist eine Kunst, und es ist hilfreich, bereits beim Entwurf des Softwaresystems an die fein granulierten Tests zu denken.
Wenn Sie Objekte mit PHPUnit testen, so greifen Sie dabei nur auf die öffentliche Schnittstelle des Objekts zu. Tests auf der Basis des öffentlich sichtbaren Verhaltens helfen Ihnen, schwierige Designprobleme frühzeitig anzugehen und zu lösen, bevor die Folgen eines mangelhaften Entwurfs größere Teile des Softwaresystems infizieren können.
PHPUnit sollte mit Hilfe des PEAR Installers installiert werden. Dieses Werkzeug bildet die Grundlage von PEAR, einem Framework und Verteilungssystem für wiederverwendbare PHP-Komponenten, und ist seit Version 4.3.0 fester Bestandteil der PHP-Distribution.
Der PEAR-Kanal (pear.phpunit.de), der das PHPUnit-Paket
bereit stellt, muss der lokalen PEAR-Umgebung zunächst bekannt gemacht
werden:
pear channel-discover pear.phpunit.deDieser Befehl ist nur einmalig vor der ersten Installation auszuführen. Nun kann der PEAR Installer verwendet werden, um PHPUnit zu installieren:
pear install phpunit/PHPUnit
Bestehende Installationen der PHPUnit und
PHPUnit2 Pakete des pear.php.net
Kanals sind vor der Installation zu entfernen.
Nach der Installation liegen die PHPUnit-Dateien in Ihrem lokalen
PEAR-Verzeichnis, üblicherweise unter
/usr/lib/php/PHPUnit.
Obwohl die Verwendung des PEAR Installer die einzige unterstützte Installationsmöglichkeit darstellt, kann PHPUnit auch "von Hand" installiert werden. Hierzu führen Sie die folgenden Schritte durch:
Laden Sie ein Release-Archiv von
http://pear.phpunit.de/get und
entpacken Sie es in ein Verzeichnis, das als include_path
in Ihrer php.ini Konfigurationsdatei angegeben
ist.
Bereiten Sie das phpunit Skript vor:
Benennen Sie das pear-phpunit Skript in
phpunit um.
Ersetzen Sie den @php_bin@ String mit dem Pfad
zum PHP-Kommandozeilen-Interpreter (üblicherweise
/usr/bin/php).
Kopieren Sie das phpunit Skript in ein
Verzeichnis, das in Ihrem PATH liegt und
machen Sie es ausführbar (chmod +x phpunit).
Der textbasierte Testrunner wird über das Kommando
phpunit aufgerufen. Der folgende Code zeigt, wie Sie
mit dem textbasierten Testrunner ausführen.
phpunit ArrayTest
PHPUnit 2.3.0 by Sebastian Bergmann.
..
Time: 0.067288
OK (2 tests)Für jeden ausgeführten Test gibt der textbasierte Testrunner ein Zeichen aus, um den Fortschritt des Testvorgangs anzuzeigen:
.Wird ausgegeben, wenn der Test erfolgreich ausgeführt wurde.
FWird ausgegeben, wenn bei der Ausführung des Tests eine Zusicherung verletzt wurde.
EWird ausgegeben, wenn bei der Ausführung des Tests ein Fehler aufgetreten ist.
IWird ausgegeben, wenn der Test als unvollständig oder noch nicht implementiert (siehe Kapitel 7) markiert wurde.
PHPUnit unterscheidet zwischen Versagern (Failures) und Fehlern (Errors). Ein Versager repräsentiert die Verletzung einer PHPUnit-Zusicherung. Ein Fehler ist dagegen eine unerwartete Ausnahme oder ein PHP-Fehler. Diese Unterscheidung erweist sich zuweilen als nützlich, weil Fehler im Allgemeinen leichter behoben werden können, während Versager generell schwerer auszumerzen sind. Wenn Sie eine lange Liste von Problemen haben, gehen Sie am besten zuerst die Fehler an und schauen dann nach, ob nach deren Behebung noch irgendwelche Versager übrig geblieben sind.
Werfen wir einen Blick auf die Kommandozeilenparameter des textbasierten Testrunners:
phpunit --help
PHPUnit 2.3.0 by Sebastian Bergmann.
Usage: phpunit [switches] UnitTest [UnitTest.php]
--coverage-data <file> Write Code Coverage data in raw format to file.
--coverage-html <file> Write Code Coverage data in HTML format to file.
--coverage-text <file> Write Code Coverage data in text format to file.
--testdox-html <file> Write agile documentation in HTML format to file.
--testdox-text <file> Write agile documentation in Text format to file.
--log-xml <file> Log test progress in XML format to file.
--loader <loader> TestSuiteLoader implementation to use.
--skeleton Generate skeleton UnitTest class for Unit in Unit.php.
--wait Waits for a keystroke after each test.
--help Prints this usage information.
--version Prints the version and exits.phpunit UnitTest
Führt die Tests der Testfall-Klasse UnitTest
aus. Die Deklaration dieser Klasse wird in der Quelltext-Datei
UnitTest.php erwartet.
Die Klasse UnitTest muss entweder
PHPUnit2_Framework_TestCase als Elternklasse
haben oder eine public static suite() Methode zur
Verfügung stellen, die ein Objekt vom Typ
PHPUnit2_Framework_Test zurückliefert. Dies kann
beispielsweise ein Objekt der Klasse
PHPUnit2_Framework_TestSuite sein.
phpunit UnitTest UnitTest.php
Führt die Tests der Testfall-Klasse UnitTest
aus. Die Deklaration dieser Klasse wird in der angegebenen
Quelltext-Datei erwartet.
--coverage-data, --coverage-htmlKontrollieren die Sammlung und Auswertung von Code-Coverage-Informationen für die ausgeführten Tests, die Thema von Kapitel 9 sind.
--testdox-html und --testdox-textErzeugen agile Dokumentation in HTML- oder Text-Format für die ausgeführten Tests. Weitere Informationen hierzu finden Sie in Kapitel 11.
--log-xmlErzeugt ein XML-Protokoll für die ausgeführten Tests.
Das folgende Beispiel zeigt das XML-Protokoll für die Tests der
Klasse ArrayTest:
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="ArrayTest" tests="2" failures="0" errors="0" time="0.020026">
<testcase name="testNewArrayIsEmpty" class="ArrayTest" time="0.014449"/>
<testcase name="testArrayContainsAnElement" class="ArrayTest" time="0.005577"/>
</testsuite>
</testsuites>
Dieses XML-Protokoll wurde für zwei Tests -- testFailure
und testError -- einer Testfallklasse
FailureErrorTest erzeugt. Es zeigt, wie Versager
und Fehler protokolliert werden:
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="FailureErrorTest" tests="2" failures="1" errors="1" time="0.013603">
<testcase name="testFailure" class="FailureErrorTest" time="0.011872">
<failure message="" type="PHPUnit2_Framework_AssertionFailedError"></failure>
</testcase>
<testcase name="testError" class="FailureErrorTest" time="0.001731">
<error message="" type="Exception"></error>
</testcase>
</testsuite>
</testsuites>
--loader
Gibt die zu verwendende Implementierung von
PHPUnit2_Runner_TestSuiteLoader an.
Der Standard-Testsuite-Lader sucht nach der Quelltext-Datei im
aktuellen Verzeichnis sowie in jedem Verzeichnis, das über die
PHP-Konfigurationsdirektive include_path
angegeben ist. Den PEAR-Konventionen folgend wird die
Klasse Project_Package_Class in der
Quelltext-Datei Project/Package/Class.php
erwartet.
--skeleton
Erzeugt ein Skelett einer Testfall-Klasse UnitTest
(in UnitTest.php) für eine Klasse
Unit (in Unit.php). Für
jede Methode der Klasse Unit wird ein
unvollständiger Test (siehe Kapitel 7) in
UnitTest erzeugt.
Das folgende Beispiel zeigt, wie ein Testfall-Klassenskelett für
eine Klasse mit Namen Sample erzeugt wird.
phpunit --skeleton SamplePHPUnit 2.3.0 by Sebastian Bergmann. Wrote test class skeleton for Sample to SampleTest.php.phpunit SampleTestPHPUnit 2.3.0 by Sebastian Bergmann. I Time: 0.007268 There was 1 incomplete test case: 1) testSampleMethod(SampleTest) OK, but incomplete test cases!!! Tests run: 1, incomplete test cases: 1.
Wenn Sie Tests für existierenden Code schreiben, müssen Sie immer wieder die gleichen Codefragmente wie
public function testSampleMethod() {
}schreiben. PHPUnit kann Ihnen bei dieser Arbeit helfen, indem es den zu testenden Code analysiert und ein entsprechendes Testfall-Klassenskelett generiert.
--waitVeranlasst den Testrunner dazu, das Programm erst dann zu beenden, wenn Sie ein Zeichen an der Konsole eingeben. Dies ist dann hilfreich, wenn Sie die Tests in einem Fenster starten, das nur so lange offen bleibt, wie der Testrunner aktiv ist.
Wenn der getestete Code PHP-Syntaxfehler enthält kann es vorkommen, dass der textbasierte Testrunner die Ausführung abbricht, ohne das eine Fehlermeldung ausgegeben wird. Der Standard-Testsuite-Lader prüft die Quelltext-Datei der Testsuite auf PHP-Syntaxfehler, nicht aber weitere, von dieser inkludierte Quelltext-Dateien. Zukünftige Versionen von PHPUnit werden dieses Problem durch Verwendung eines Sandbox-PHP-Interpreters lösen.
Eine der zeitraubendsten Arbeiten beim Erstellen von Tests besteht darin, Code zu schreiben, mit dem die Welt vor dem Test in einen definierten Zustand versetzt und wieder in ihren ursprünglichen Zustand gebracht wird, nachdem der Test abgeschlossen worden ist. Diesen wohldefinierten Zustand bezeichnen wir als Testinventar, oder englisch fixture.
In Beispiel 2.1 bestand das
Testinventar lediglich aus einem einfachen array,
das in der Variablen $fixture abgelegt wurde. In
den meisten Fällen wird das Testinventar allerdings komplexer aufgebaut
sein, und der für das Vorbereiten des Testinventars benötigte Code wird
entsprechend wachsen. Der eigentliche Inhalt des Tests verschwindet
dann zwischen dem vielen Code, der für den Aufbau eines vorhersagbaren
Zustands der Welt benötigt wird, in dem die Tests laufen sollen. Noch
größer wird das Problem, wenn Sie mehrere Tests mit ähnlicher
Initialisierung schreiben wollen. Wenn uns das Test-Framework nicht
dabei helfen würde, müssten wir das obige Muster für jeden zu
schreibenden Test duplizieren.
PHPUnit hilft uns dabei, den Initialisierungscode mehrfach zu verwenden.
Bevor eine Testmethode läuft, wird eine Schablonenmethode namens
setUp() aufgerufen. setUp() ist
dafür vorgesehen, dass Sie dort die Objekte erzeugen, mit denen Sie die
Tests durchführen wollen. Wenn ein Test ausgeführt worden ist, wird
unabhängig davon, ob er erfolgreich war oder nicht, eine weitere
Schablonenmethode namens tearDown() aufgerufen.
Diese ist dafür gedacht, die Objekte, mit denen der Test durchgeführt
worden ist, aufzuräumen.
Sie können nun Beispiel 2.1
verbessern, indem Sie in setUp() eine Instanzvariable
$fixture vorbereiten, die in den Testmethoden
anstelle einer Methoden-lokalen Variablen verwendet wird. Hiermit
verhindern Sie die Code-Duplikation, die vorher bestand.
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
class ArrayTest extends PHPUnit2_Framework_TestCase {
protected $fixture;
protected function setUp() {
// Array-Fixture erzeugen.
$this->fixture = array();
}
public function testNewArrayIsEmpty() {
// Der erwartete Wert von sizeof($this->fixture) ist 0.
$this->assertEquals(0, sizeof($this->fixture));
}
public function testArrayContainsAnElement() {
// Ein Element dem Array hinzufügen.
$this->fixture[] = 'Element';
// Der erwartete Wert von sizeof($this->fixture) ist 1.
$this->assertEquals(1, sizeof($this->fixture));
}
}
?>
Bei jedem Durchlauf einer Testmethode werden setUp()
und tearDown() genau einmal aufgerufen. Es mag
sparsamer erscheinen, diese Methoden für alle Testaufrufe einer
Testfall-Klasse nur einmal aufzurufen, aber es wäre dann sehr viel
schwieriger, Tests zu schreiben, die vollständig unabhängig
voneinander sind.
Abgesehen davon, dass für jede Testmethode setUp()
und tearDown() aufgerufen werden, laufen die
Testmethoden auch jeweils mit frisch erzeugten Instanzen der
Testfall-Klasse (siehe Kapitel 13).
In der Theorie sehen setUp() und
tearDown() schön symmetrisch aus, in der Praxis
sind sie es aber nicht. Sie müssen tearDown()
tatsächlich nur implementieren, wenn Sie in setUp()
externe Ressourcen wie Dateien oder Sockets alloziert haben. Dient
Ihr setUp() nur dazu, einfache PHP-Objekte zu
allozieren, so können Sie im Allgemeinen tearDown()
ignorieren. Wenn Sie allerdings viele Objekte in Ihrem
setUp() erzeugen, so sollten Sie die auf diese
Objekte verweisenden Variablen mit unset() leeren,
damit sie von der Garbage Collection erfasst werden können. Die
Testfall-Objekte werden nämlich zu keiner vorhersagbaren Zeit der
Garbage Collection unterzogen.
Wie gehen Sie vor, wenn Sie zwei Tests mit nur geringfügig unterschiedlichen Initialisierungen haben? Hierfür gibt es zwei Möglichkeiten:
Wenn die Unterschiede im setUp() gering sind,
verschieben Sie den variablen Teil aus setUp()
in die Testmethode.
Wenn setUp() wirklich unterschiedlich ist,
benötigen Sie eine andere Testfall-Klasse. Benennen Sie die Klassen
entsprechend ihrer unterschiedlichen Initialisierung.
Für die Initialisierung auf der Ebene der Testreihe bietet PHPUnit keine besonders komfortable Unterstützung. Es gibt durchaus Gründe dafür, Teile des Inventars von mehreren Tests gemeinsam nutzen zu lassen. Allerdings ist der eigentliche Grund für die Nutzung von gemeinsam genutztem Testinventar in der Regel ein ungelöstes Designproblem.
Ein gutes Beispiel dafür, wann es sinnvoll ist, Teile des Inventars von
mehreren Tests gemeinsam nutzen zu lassen, sind Tests, die mit einer
Datenbank interagieren. So gibt es beispielsweise die Möglichkeit,
sich nur einmal bei einer Datenbank anzumelden und die resultierende
Datenbankverbindung in verschiedenen Tests zu verwenden. Das
beschleunigt Ihre Tests natürlich. Zu diesem Zweck verpacken Sie die
Testreihe, die Sie beim Aufruf der Klassenmethode
suite() erhalten, in einem Objekt der Klasse
PHPUnit2_Extensions_TestSetup, das die Methoden
setUp() und tearDown() so
überschreibt, dass darin eine Datenbankverbindung hergestellt bzw.
wieder geschlossen wird.
Beispiel 5.1
zeigt mit DatabaseTestSetup eine mögliche
Implementierung von PHPUnit2_Extensions_TestSetup.
Die Tests der Testfall-Klasse DatabaseTests können
beispielsweise mit phpunit DatabaseTestSetup über
den Test-basierten Testrunner so ausgeführt werden, dass sie von
DatabaseTestSetup dekoriert werden.
Beispiel 5.1: Initialisierung auf Testreihen-Ebene
<?php
require_once 'PHPUnit2/Framework/TestSuite.php';
require_once 'PHPUnit2/Extensions/TestSetup.php';
class DatabaseTestSetup extends PHPUnit2_Extensions_TestSetup {
protected $connection = NULL;
protected function setUp() {
$this->connection = new PDO(
'mysql:host=wopr;dbname=test',
'root',
''
);
}
protected function tearDown() {
$this->connection = NULL;
}
public static function suite() {
return new DatabaseTestSetup(
new PHPUnit2_Framework_TestSuite('DatabaseTests')
);
}
}
?>
Es kann aber nicht oft genug darauf hingewiesen werden, dass es den Wert Ihres Tests vermindert, wenn Sie dasselbe Inventar in mehreren Tests verwenden. Das hier zugrunde liegende Designproblem besteht darin, dass die Objekte zu eng miteinander verknüpft sind. Es wäre günstiger, erst das Design zu verbessern und dann Tests mit Hilfe von Stubs (siehe Kapitel 10) zu schreiben. Indem Sie Laufzeit-Abhängigkeiten herstellen, verpassen Sie eine Gelegenheit zur Verbesserung des Designs.
PHPUnit bietet zwei Erweiterungen der Standard-Basisklasse für
Testfall-Klassen, PHPUnit2_Framework_TestCase,
die beim Schreiben von Tests für Ausnahmen und
Ausführungsgeschwindigkeit helfen.
Wie werden Ausnahmen getestet? Es lässt sich keine Zusicherung (Assertion) formulieren, die prüft, ob eine Ausnahme ausgelöst worden ist. Stattdessen müssen Sie beim Schreiben des Tests die durch PHP gegebenen Möglichkeiten zur Ausnahmenbehandlung nutzen.
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
class ExceptionTest extends PHPUnit2_Framework_TestCase {
public function testException() {
try {
// ... Code, von dem wir das Auslösen einer Ausnahme erwarten ...
}
catch (Exception $expected) {
return;
}
$this->fail('Eine erwartete Ausnahme wurde nicht ausgelöst.');
}
}
?>
Wenn der Code, von dem wir das Auslösen einer Ausnahme erwarten,
keine Ausnahme auslöst, stoppt der nachfolgende Aufruf von
fail()
(siehe Tabelle 14.2)
den Test und signalisiert das Problem. Wird dagegen die Ausnahme
wie erwartet ausgelöst, so kommt der catch-Block
zur Ausführung und der Testlauf wird erfolgreich beendet.
Alternativ können Sie Ihre Testfall-Klasse von
PHPUnit2_Extensions_ExceptionTestCase ableiten um
zu testen, ob eine erwartete Ausnahme vom getesteten Code ausgelöst
wird.
Beispiel 6.1
zeigt, wie Sie mit der Methode setExpectedException()
die erwartete Ausnahme setzen. Wird diese Ausnahme während der
Ausführung der Testmethode nicht ausgelöst, so wird der Test als
Failure gewertet.
Beispiel 6.1: PHPUnit2_Extensions_ExceptionTestCase verwenden
<?php
require_once 'PHPUnit2/Extensions/ExceptionTestCase.php';
class ExceptionTest extends PHPUnit2_Extensions_ExceptionTestCase {
public function testException() {
$this->setExpectedException('Exception');
}
}
?>
phpunit ExceptionTest
PHPUnit 2.3.0 by Sebastian Bergmann.
F
Time: 0.006798
There was 1 failure:
1) testException(ExceptionTest)
Expected exception Exception
FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0, Incomplete Tests: 0.
Tabelle 6.1
führt das externe Protokoll von
PHPUnit2_Extensions_ExceptionTestCase auf.
Tabelle 6.1. Das externe Protokoll von PHPUnit2_Extensions_ExceptionTestCase
| Methode | Aktion |
|---|---|
void setExpectedException(string $exceptionName) | Setzt den Namen der erwarteten Ausnahme. |
String getExpectedException() | Liefert den Namen der erwarteten Ausnahme. |
Sie können Ihre Testfall-Klasse von
PHPUnit2_Extensions_PerformanceTestCase ableiten, um
zu testen, ob ein Funktions- oder Methodenaufruf innerhalb eines
angegebenen Zeitlimits ausgeführt wird.
Beispiel 6.2
zeigt, wie Sie mit der Methode setMaxRunningTime()
ein Zeitlimit für die Ausführung der Testmethode festlegen können. Wird
die Ausführung der Testmethode nicht innerhalb dieses Zeitlimits
abgeschlossen, so wird der Test als Failure gewertet.
Beispiel 6.2: PHPUnit2_Extensions_PerformanceTestCase verwenden
<?php
require_once 'PHPUnit2/Extensions/PerformanceTestCase.php';
class PerformanceTest extends PHPUnit2_Extensions_PerformanceTestCase {
public function testPerformance() {
$this->setMaxRunningTime(2);
sleep(1);
}
}
?>
Tabelle 6.2
führt das externe Protokoll von
PHPUnit2_Extensions_PerformanceTestCase auf.
Tabelle 6.2. Das externe Protokoll von PHPUnit2_Extensions_PerformanceTestCase
| Methode | Aktion |
|---|---|
void setMaxRunningTime(int $maxRunningTime) | Setzt die maximale Ausführungszeit des Tests auf $maxRunningTime (in Sekunden). |
integer getMaxRunningTime() | Liefert die maximale Ausführungszeit des Tests. |
Wenn Sie eine neue Testfall-Klasse schreiben, so möchten Sie vielleicht mit leeren Testmethoden wie
public function testSomething() {
} beginnen, um so den Überblick darüber zu behalten, welche
Tests Sie schreiben müssen. Das Problem mit leeren Testmethoden ist,
dass sie vom PHPUnit-Framework als erfolgreich gewertet werden. Diese
Missinterpretation führt dazu, dass Ihre Testreports nutzlos werden: Sie
können nicht erkennen, ob ein Test tatsächlich erfolgreich durchlaufen
wurde, oder ob er nur noch nicht implementiert ist. Das Aufrufen von
$this->fail() in der noch nicht implementierten
Testmethode hilft ebenfalls nicht, da der Test dann als fehlgeschlagen
gewertet würde. Dies wäre genauso falsch wie die Interpretation eines
noch nicht implementierten Tests als Erfolg.
Wenn wir uns einen erfolgreichen Test als "grünes Lämpchen" und einen
fehlgeschlagenen Test als "rotes Lämpchen" vorstellen, so benötigen wir
also zusätzlich ein "gelbes Lämpchen", um einen Test als unvollständig
oder noch nicht implementiert markieren zu können.
PHPUnit2_Framework_IncompleteTest ist eine
Schnittstelle, mit der eine Ausnahme markiert werden kann. Wird eine
solche Ausnahme in einer Testmethode ausgelöst, wird der Test als
unvollständing oder noch nicht implementiert markiert.
PHPUnit2_Framework_IncompleteTestError ist die
Standard-Implementierung dieser Markierer-Schnittstelle.
Beispiel 7.1 zeigt die
Testfall-Klasse SampleTest. Diese enthält eine
Testmethode testSomething(), in der die Ausname
PHPUnit2_Framework_IncompleteTestError ausgelöst
wird, um den Test als unvollständing zu markieren.
Beispiel 7.1: Einen Test als unvollständig markieren
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
require_once 'PHPUnit2/Framework/IncompleteTestError.php';
class SampleTest extends PHPUnit2_Framework_TestCase {
public function testSomething() {
// Optional: Testen Sie hier, was Sie möchten.
$this->assertTrue(TRUE, 'Dies sollte bereits funktionieren.');
// Test anhalten und als unvollständig markieren.
// Hierzu können Sie jede Ausnahme verwenden, die die
// Schnittstelle PHPUnit2_Framework_IncompleteTest implementiert.
throw new PHPUnit2_Framework_IncompleteTestError(
'Dieser Test ist noch nicht fertig ausprogrammiert.'
);
}
}
?>
Ein unvollständiger Test wird mit einem I in der
Ausgabe des textbasierten Testrunners angezeigt, wie folgendes
Beispiel zeigt:
phpunit SampleTest
PHPUnit 2.3.0 by Sebastian Bergmann.
I
Time: 0.006657
There was 1 incomplete test case:
1) testSomething(SampleTest)
Dieser Test ist noch nicht fertig ausprogrammiert.
OK, but incomplete test cases!!!
Tests run: 1, incomplete test cases: 1.Unit-Tests sind essentieller Bestandteil verschiedener Software-Entwicklungsprozesse wie Test-First-Programmierung, Extreme Programming und testgetriebener Entwicklung. Ferner ermöglichen Sie die Entwicklung im Stile von Design-by-Contract in Programmiersprachen wie PHP, die diese Methode nicht auf Sprachebene unterstützen.
Auch wenn Sie PHPUnit verwenden, können Sie Ihre Tests erst dann schreiben, wenn Sie mit dem Programmieren fertig sind. Erinnern Sie sich aber an die Erkenntnis, dass Tests umso wertvoller sind, je kürzer der Abstand zwischen ihrer Ausführung und dem Zeitpunkt der potentiellen Entstehung eines Fehlers ist. Anstatt also die Tests erst Monate nach dem Abschluss der Programmierarbeiten zu schreiben, können Sie sie auch Tage oder Stunden oder Minuten nach dem potentiellen Einfügen eines Mangels in den Code schreiben. Aber warum nicht sogar noch weiter gehen? Warum nicht Tests noch etwas früher schreiben, und zwar bevor überhaupt ein möglicher Fehler entstehen kann?
Test-First-Programmierung als Bestandteil von Extreme Programming und testgetriebener Entwicklung baut auf dieser Idee auf. Die heutige Computertechnik gibt uns die Möglichkeit, tausendmal am Tag Tausende von Tests auszuführen. Die Rückmeldungen aus allen diesen Tests ermöglichen es uns, in kleinen Schritten zu programmieren und jeden dieser Schritte durch einen neuen automatisierten Test abzusichern, den wir den bereits bestehenden Tests hinzufügen. Die Tests haben für Sie dieselbe Funktion wie die Felshaken für Bergsteiger: Sie geben Ihnen die Sicherheit, dass Sie, egal was passiert, immer nur das letzte Stück zurückfallen können.
Wenn man die Tests zuerst schreibt, lassen sie sich zunächst nicht ausführen, da sie Objekt und Methoden verwenden, die noch nicht definiert sind. Dies mag einem zunächst merkwürdig vorkommen, aber mit der Zeit gewöhnt man sich daran. Die Test-First-Programmierung bietet eine pragmatische Möglichkeit, um das Prinzip der Programmierung gegen eine Schnittstelle an Stelle einer Implementierung umzusetzen: Während Sie die Tests schreiben denken Sie über die Schnittstelle des Objekts nach, das Sie gerade testen -- wie sieht dieses Objekt von aussen aus? Wenn Sie daran gehen, den tatsächlichen Code zu schreiben, der hinter dem getesteten Objekt steht, denken Sie nur über die Implementierung nach. Die Schnittstelle ist durch den fehlschlagenden Test festgelegt.
Was nun folgt, ist eine zwangsläufig verkürzte Einführung in die Test-First-Programmierung. Es gibt andere Bücher, mit deren Hilfe Sie sich tiefer in dieses Thema einarbeiten können, zum Beispiel Test-Driven Development: By Example [Beck2002] von Kent Beck oder Test-Driven Development: A Practical Guide [Astels2003] von Dave Astels.
Als Beispiel betrachten wir eine Klasse, die ein Bankkonto repräsentieren soll. Der Vertrag für diese Klasse sieht nicht nur Methoden für den lesenden und schreibenden Zugriff auf das Bankkonto vor, sondern auch die Einhaltung der beiden folgenden Bedingungen:
Der Kontostand ist zu Beginn null.
Der Kontostand kann nicht negativ werden.
Der Test-First-Programmierung folgend, schreiben Sie die Tests für
die Klasse BankAccount bevor Sie die Klasse selber
schreiben. Sie benutzen die Vertragsbedingungen als Ausgangspunkt für
die Tests und benennen die Testmethoden entsprechend, wie in
Beispiel 8.1
gezeigt.
Beispiel 8.1: Tests für die BankAccount-Klasse
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
require_once 'BankAccount.php';
class BankAccountTest extends PHPUnit2_Framework_TestCase {
private $ba;
protected function setUp() {
$this->ba = new BankAccount;
}
public function testBalanceIsInitiallyZero() {
$this->assertEquals(0, $this->ba->getBalance());
}
public function testBalanceCannotBecomeNegative() {
try {
$this->ba->withdrawMoney(1);
}
catch (Exception $e) {
return;
}
$this->fail();
}
public function testBalanceCannotBecomeNegative2() {
try {
$this->ba->depositMoney(-1);
}
catch (Exception $e) {
return;
}
$this->fail();
}
public function testBalanceCannotBecomeNegative3() {
try {
$this->ba->setBalance(-1);
}
catch (Exception $e) {
return;
}
$this->fail();
}
}
?>
Nun schreiben Sie den minimal benötigten Code, damit der erste Test,
testBalanceIsInitiallyZero(), erfolgreich laufen
kann. Dies bedeutet, dass Sie die Methode getBalance()
der Klasse BankAccount, wie in
Beispiel 8.2
gezeigt, implementieren.
Beispiel 8.2: Benötigter Code, damit der erste Test erfolgreich laufen kann
<?php
class BankAccount {
private $balance = 0;
public function getBalance() {
return $this->balance;
}
}
?>
Der Test für die erste Vertragsbedingung läuft nun erfolgreich. Die
Tests für die zweite Vertragsbedingung schlagen allerdings noch fehl,
da die entsprechenden Methoden der Klasse BankAccount
noch nicht implementiert worden sind.
phpunit BankAccountTest
PHPUnit 2.3.0 by Sebastian Bergmann.
.
Fatal error: Call to undefined method BankAccount::withdrawMoney()
Damit die Tests, die die Einhaltung der zweiten Vertragsbedingung
überwachen, erfolgreich laufen können, müssen Sie die Methoden
withdrawMoney(), depositMoney()
und setBalance() der Klasse
BankAccount, wie in
Beispiel 8.3
gezeigt, implementieren. Diese Methoden werden so implementiert, dass sie
eine InvalidArgumentException auslösen, wenn sie mit
Parameterwerten, die den Vertrag verletzen würden, aufgerufen werden.
Beispiel 8.3: Die vollständige BankAccount-Klasse
<?php
class BankAccount {
private $balance = 0;
public function getBalance() {
return $this->balance;
}
public function setBalance($balance) {
if ($balance >= 0) {
$this->balance = $balance;
} else {
throw new InvalidArgumentException;
}
}
public function depositMoney($amount) {
if ($amount >= 0) {
$this->balance += $amount;
} else {
throw new InvalidArgumentException;
}
}
public function withdrawMoney($amount) {
if ($amount >= 0 && $this->balance >= $amount) {
$this->balance -= $amount;
} else {
throw new InvalidArgumentException;
}
}
}
?>
Die Tests für die zweite Vertragsbedingung laufen nun ebenfalls erfolgreich:
phpunit BankAccountTest
PHPUnit 2.3.0 by Sebastian Bergmann.
....
Time: 0.057038
OK (4 tests)
Alternativ können Sie die statischen Methoden der Klasse
PHPUnit2_Framework_Assert verwenden, um die
Zusicherungen im Stile von Design-by-Contract in den Code zu
schreiben, wie in
Beispiel 8.4
gezeigt. Wenn eine Zusicherung fehlschlägt wird eine Ausnahme vom Typ
PHPUnit2_Framework_AssertionFailedError ausgelöst.
Auf diese Weise müssen Sie weniger Code für die Überprüfung der
Parameterwerte schreiben und die Tests werden lesbarer. Allerdings
fügen Sie Ihrem Projekt eine Laufzeitabhängigkeit auf PHPUnit hinzu.
Beispiel 8.4: Die BankAccount-Klasse mit Zusicherungen im Stile von Design-by-Contract
<?php
require_once 'PHPUnit2/Framework/Assert.php';
class BankAccount {
private $balance = 0;
public function getBalance() {
return $this->balance;
}
public function setBalance($balance) {
PHPUnit2_Framework_Assert::assertTrue($balance >= 0);
$this->balance = $balance;
}
public function depositMoney($amount) {
PHPUnit2_Framework_Assert::assertTrue($amount >= 0);
$this->balance += $amount;
}
public function withdrawMoney($amount) {
PHPUnit2_Framework_Assert::assertTrue($amount >= 0);
PHPUnit2_Framework_Assert::assertTrue($this->balance >= $amount);
$this->balance -= $amount;
}
}
?>
Durch das Schreiben der Vertragsbedingungen in die Tests haben wir
Design-by-Contract benutzt, um die BankAccount-Klasse
zu entwerfen. Danach haben wir, dem Ansatz der Test-First-Programmierung
folgend, nur soviel Code geschrieben, wie für das erfolgreiche
Durchlaufen der Tests nötig ist. Allerdings haben wir vergessen, Tests
zu schreiben, die setBalance(),
depositMoney() und withdrawMoney()
mit zulässigen Parametern aufrufen. Wir benötigen ein Hilfsmittel, um
die Qualität unserer Tests zu überprüfen. Ein solches Hilfsmittel ist
die Code-Coverage-Analyse, die wir im nächsten Kapitel kennenlernen
werden.
Sie haben gelernt, wie Sie mit Unit-Tests Ihren Code testen können. Aber wie testen Sie Ihre Tests? Wie finden Sie Codeabschnitte, für die Sie noch keine Tests haben -- oder, anders ausgedrückt, die noch nicht durch einen Test abgedeckt (englisch: covered) sind? Wie messen Sie die Vollständigkeit der Tests? All diese Fragen werden durch die Code-Coverage-Analyse beantwortet. Diese gibt Ihnen Aufschluss darüber, welche Teile des Produktionscodes ausgeführt werden, wenn die Tests laufen.
Die Code-Coverage-Analyse von PHPUnit stützt sich auf die Statement-Coverage-Funktionalität der Xdebug-Erweiterung. Unter Statement-Coverage verstehen wir, dass eine Methode, die 100 Code-Zeilen umfasst, von denen nur 75 während der Ausführung der Tests ausgeführt werden, eine Code-Coverage von 75 Prozent aufweist.
Abbildung 9.1 zeigt
einen Code-Coverage-Report im HTML-Format für die
BankAccount-Klasse aus
Beispiel 8.3,
der mit dem textbasierten Testrunner und der Kommandozeilenoption
--coverage-html erzeugt wurde. Ausführbare
Code-Zeilen sind schwarz, nicht ausführbare Code-Zeilen sind grau.
Tatsächlich ausgeführte Code-Zeilen sind hervorgehoben.
Dem Code-Coverage-Report können wir entnehmen, dass wir Tests schreiben
müssen, die setBalance(), depositMoney()
und withdrawMoney() mit zulässigen Parametern aufrufen,
damit die BankAccount-Klasse vollständig durch Tests
abgedeckt ist.
Beispiel 9.1
zeigt die fehlenden Tests, die wir in BankAccountTest
hinzufügen müssen.
Beispiel 9.1: Die BankAccount-Klasse, vollständig durch Tests abgedeckt
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
require_once 'BankAccount.php';
class BankAccountTest extends PHPUnit2_Framework_TestCase {
// ...
public function testSetBalance() {
$this->ba->setBalance(1);
$this->assertEquals(1, $this->ba->getBalance());
}
public function testDepositAndWidthdrawMoney() {
$this->ba->depositMoney(1);
$this->assertEquals(1, $this->ba->getBalance());
$this->ba->withdrawMoney(1);
$this->assertEquals(0, $this->ba->getBalance());
}
}
?>
In Abbildung 9.2
sehen wir, dass die BankAccount-Klasse nun
vollständig durch Tests abgedeckt ist.
In Kapitel 12 werden Sie lernen, wie Sie mit Phing ausführlichere Code-Coverage-Reports erstellen können.
Tests, die nur einen Gegenstand prüfen sind informativer als Tests, bei denen ein Versagen viele Ursachen haben kann. Wie aber können Sie Ihre Tests von äußeren Einflüssen isolieren? Ersetzen Sie einfach alle teuren, undurchsichtigen, unzuverlässigen, langsamen und komplizierten Ressourcen durch Stubs aus einfachen PHP-Objekten. Beispielsweise können Sie Elemente, die in Wirklichkeit komplizierte Berechnungen erfordern, zumindest für einzelne Tests durch die Rückgabe von Konstanten ersetzen.
Ein Problem, das mit Hilfe von Stubs gelöst werden kann, ist die
Allokation teurer externer Ressourcen. Das gemeinsame Nutzen einer
Datenbankverbindung durch mehrere Tests, beispielsweise durch
Verwendung des PHPUnit2_Extensions_TestSetup-Dekorierers,
hilft. Die Datenbank für die Durchführung der Tests überhaupt nicht zu
verwenden ist noch besser.
Ein Nebeneffekt der Verwendung von Stubs ist die tendenzielle
Verbesserung Ihrer Designs. Auf Ressourcen, die an vielen Stellen
benötigt werden, greifen Sie über eine einzige Fassade zu, die Sie leicht
durch einen Stub ersetzen können. Beispielsweise verwenden Sie keine über
den gesamten Code verstreuten Datenbank-Aufrufe, sondern haben ein
einziges, die Schnittstelle IDatabase
implementierendes, Database-Objekt. Dann erstellen
Sie eine Stub-Implementierung von IDatabase und
benutzen diese, wenn Sie Ihre Tests laufen lassen. Sie können sogar eine
Wahlmöglichkeit schaffen, so dass die Tests alternativ mit der wirklichen
Datenbank oder mit der Stub-Datenbank laufen und Ihre Tests sowohl für
die lokale Entwicklung als auch für Integrationstests mit der richtigen
Datenbank verwenden.
Funktionalität, die zur selben Zeit durch Stubs ersetzt werden muss, neigt dazu, sich auch im selben Objekt anzusammeln. Dies verbessert die Kohäsion. Und indem Sie die Funktionalität in einer einzigen, kohärenten Schnittstelle präsentieren, entkoppeln Sie diese besser vom Rest des Systems.
Manchmal müssen Sie überprüfen, ob ein Objekt korrekt aufgerufen worden ist. Dazu können Sie für das aufzurufende Objekt einen ausgewachsenen Stub erstellen. Dann wiederum kann es aber schwierig sein, auf korrekte Ergebnisse zu prüfen. Eine einfachere Lösung besteht darin, das Testfall-Objekt selbst als Stub zu benutzen. Dies bezeichnet man als Self-Shunting. Der Begriff ist von einem in der Medizin üblichen Verfahren übernommen worden, bei dem Blut durch einen Schlauch von einer Arterie in eine Vene geleitet wird, um eine bequeme Möglichkeit zur Injektion von Medikamenten zu schaffen.
Dafür folgt nun ein Beispiel. Angenommen, Sie möchten testen, ob die
korrekte Methode eines Objekts aufgerufen wird, das ein anderes Objekt
beobachtet. Zunächst machen Sie aus der Testfall-Klasse einen
Implementor von Observer:
class ObserverTest extends PHPUnit2_Framework_TestCase implements Observer {
}
Danach implementieren Sie update(), die einzige
Methode in Observer, um sicherzustellen, dass diese
aufgerufen wird, wenn sich der Zustand des beobachteten Objekts
(Subject) ändert:
public $wasCalled = FALSE;
public function update(Subject $subject) {
$this->wasCalled = TRUE;
}
Jetzt können Sie den Test schreiben. Erzeugen Sie ein neues Objekt
der Klasse Subject und registrieren das
Testfall-Objekt als Beobachter. Wenn sich der Zustand des
Subject-Objekts (beispielsweise durch Aufruf einer
Methode doSomething()) ändert, muss dieses die
update()-Methoden der registrierten Beobacher-Objekte
aufrufen. Wir benutzen die Instanzvariable $wasCalled,
die von unserer Implementierung von update() gesetzt
wird, um zu testen, ob sich das Subject-Objekt wie
erwartet verhält.
public function testUpdate() {
$subject = new Subject;
$subject->attach($this);
$subject->doSomething();
$this->assertTrue($this->wasCalled);
}
Beachten Sie, dass wir ein neues Subject-Objekt
anstelle einer globalen Instanz verwenden. Ein solcher Entwurfsstil,
der die Entkopplung zwischen den Objekten verbessert und damit die
Wiederverwendbarkeit erleichtert, wird duch die Verwendung von Stubs
gefördert.
Wer mit dem Self-Shunt-Pattern nicht vertraut ist, für den können die Tests schwer lesbar sein. Was geschieht hier? Wie kann ein Testfall zugleich ein Beobachter sein? Wenn Sie sich aber erst einmal an diese Vorgehensweise gewöhnt haben, sind die Tests leicht zu lesen. Alles, was Sie zum Verständnis eines Tests benötigen, befindet sich in einer einzigen Klasse.
Wenn Sie sich erst einmal daran gewöhnt haben, automatisierte Tests zu schreiben, werden Sie vermutlich weitere Anwendungsmöglichkeiten dafür entdecken. Hier sind einige Beispiele.
In einem Projekt, das mit einem agilen Software-Entwicklungsprozess wie dem Extreme Programming entwickelt wird, kommt es häufig vor, dass die Dokumentation nicht mit den Änderungen an Code und Design Schritt halten kann. Extreme Programming verlangt kollektives Eigentum des Codes. Daher müssen alle Entwickler wissen, wie das gesamte System funktioniert. Wenn Sie diszipliniert genug sind und konsequent "sprechende Namen" für Ihre Tests verwenden, können Sie mit Hilfe der TestDox-Funktionalität von PHPUnit automatisch Dokumentation für Ihr Projekt auf Grundlage der Tests erstellen. Diese Dokumentation gibt den Entwicklern einen Überblick über die Klassen des Projekts, und was von ihnen erwartet wird.
Die TestDox-Funktionalität von PHPUnit betrachtet die Testmethoden und
erzeugt aus der Camel-Case-Notation der PHP-Namen Sätze: aus
testBalanceIsInitiallyZero() wird "Balance is initially
zero". Gibt es mehrere Testmethoden, deren Namen sich nur durch eine
Ziffer am Ende unterscheiden (beispielsweise
testBalanceCannotBecomeNegative() und
testBalanceCannotBecomeNegative2()), so wird der
entsprechende Satz ("Balance cannot become negative") nur einmal
erzeugt, und zwar dann, wenn alle diese Testmethoden erfolgreich
durchlaufen wurden.
Das folgende Beispiel zeigt die mit
phpunit --testdox-text BankAccountTest.txt BankAccountTest
für die Tests aus
Beispiel 8.1
erstellte agile Dokumentation.
BankAccount - Balance is initially zero - Balance cannot become negative
Alternativ kann die agile Dokumentation mit
--testdox-html BankAccountTest.htm auch im
HTML-Format erstellt werden.
Wenn Sie sich dazu entschließen, bei der Entwicklung ein externes Paket einzusetzen, gehen Sie damit ein gewisses Risiko ein. Es ist nicht auszuschließen, dass sich das Paket nicht so verhält, wie Sie es erwarten, und dass sich zukünftige Versionen in subtiler Weise so verändern, dass Ihre Programme nicht mehr funktionieren, ohne dass Sie es merken. Eine Möglichkeit, dieses Problem zu lösen, besteht darin, Annahmen über die Funktionsweise des externen Paketes durch Tests und agile Dokumentation zu dokumentieren: Jedes Mal, wenn Sie eine Annahme treffen, schreiben Sie einen Test. Wird der Test bestanden, ist Ihre Prämisse gültig. Wenn Sie die erforderliche Disziplin aufbringen und alle Annahmen durch Tests ausdrücken, dann brauchen Sie in der Zukunft neue Versionen des externen Paketes nicht zu fürchten. Sie lassen Ihre Testreihe laufen, und wenn diese erfolgreich verläuft, so können Sie davon ausgehen, dass Ihre Programme weiterhin wie erwartet funktionieren. Andernfalls müssen Sie näher untersuchen, was sich geändert hat.
Wenn Sie Tests dazu verwenden, Prämissen zu dokumentieren, gehören die Tests Ihnen. In diesem Fall haben Sie mit dem Lieferanten des Pakets, auf das Sie sich verlassen können müssen, eine eher distanzierte Beziehung. Ist die Beziehung zu dem Hersteller aber enger oder streben Sie eine engere Beziehung an, so können Tests eine Möglichkeit zur Koordination Ihrer Aktivitäten sein.
Können Sie sich mit dem Ersteller auf eine API einigen, ist es Ihnen möglich, die Tests gemeinsam zu schreiben und zu pflegen. Sie setzen sich mit dem Lieferanten zusammen und codieren die Tests so, dass sie so viele Prämissen wie möglich deutlich machen. Versteckte Anforderungen sind der Tod jeder Kooperation. Anhand der Tests weiß der Hersteller genau, was von ihm erwartet wird. Er braucht erst wiederzukommen, wenn alle Tests erfolgreich laufen.
Mit dem Konzept der Stubs aus dem vorigen Kapitel können Sie die beiden Teams noch weiter entkoppeln:
Die Aufgabe des Herstellerteams besteht darin, die Tests zum Laufen zu bringen, indem es die Stub-Objekte durch reale Implementierungen ersetzt.
Ihre Aufgabe ist es, dafür zu sorgen, dass Ihr eigener Code mit den Stub-Objekten funktioniert, bis er schließlich die eigentliche Implementierung erhält.
Auf diese Weise können beide Teams unabhängig voneinander arbeiten.
Wenn Sie eine Fehlermeldung erhalten, so mag Ihre spontane Reaktion darauf sein, den Fehler so schnell wie möglich zu beheben. Diese Neigung ist aber selten hilfreich, da es sehr wahrscheinlich ist, dass Sie mit Ihrer "Reparatur" einen anderen Fehler verursachen.
Tests bieten eine Möglichkeit, diesen spontane Drang im Zaum zu halten.
Verifizieren Sie, dass Sie den Fehler reproduzieren können.
Finden Sie die kleinstmögliche Programmeinheit, in der sich der Fehler noch darstellen lässt. Wenn beispielsweise eine Zahl in der Ausgabe fehlerhaft dargestellt wird, suchen Sie das Objekt, das diese Zahl berechnet.
Schreiben Sie einen automatisierten Test, der jetzt versagt, aber der erfolgreich verlaufen muss, wenn der Mangel behoben ist.
Beseitigen Sie den Fehler.
Die Suche nach der kleinstmöglichen zuverlässigen Reproduktion des Fehlers gibt Ihnen die Möglichkeit, sich wirklich auf die Ursache des Problems zu konzentrieren. Der Test, den Sie schreiben, erhöht die Wahrscheinlichkeit, dass Sie durch die Reparatur wirklich nur den Fehler beseitigen. Zusätzlich vermindert der neue Test die Wahrscheinlichkeit, dass die Korrektur zu einem späteren Zeitpunkt versehentlich rückgängig gemacht wird. Die bereits existierenden Tests tragen dafür Sorge, dass die Korrektur des aktuellen Problems ihrerseits keine neuen Probleme verursacht.
Erst automatisierte Tests machen die zum Erreichen des einfachsten Designs nötigen Änderungen (Refactoring, "neu herstellen") am Code sinnvoll durchführbar. Ohne sie müsste jede Methode einer jeden Klasse von Hand getestet werden, wenn der Code einer Klasse geändert wurde. Der Vorgang des Refactoring kann in kleine Einzelschritte aufgeteilt werden, die verhaltensneutral sind.
Die folgenden Schritte helfen Ihnen, Code und Design Ihres Projekts zu verbessern, und dabei jeden Einzelschritt mit Unit-Tests abzusichern, damit das Ergebnis wirklich verhaltensneutral ist:
Alle Unit-Tests laufen.
Der Code kommuniziert alle seine Designkonzepte.
Der Code enthält keine Redundanz.
Der Code enthält, unter Berücksichtigung der obigen Regeln, die geringstmögliche Anzahl an Klassen und Methoden.
Phing (PHing Is Not GNU make) ist eine Portierung von Apache Ant, dem automatischen Build-Werkzeug der Java-Welt. Im Kontext von PHP, wo Sie Ihr Projekt nicht bauen und ihre Quelltexte nicht kompilieren müssen, ist die Intention von Phing eine andere als die des Vorbilds. Mit Phing können Sie Arbeiten wie Packaging, Deployment und Testen Ihrer Anwendung automatisieren und vereinfachen. Hierfür stellt Phing eine Reihe von Modulen, so genannte Tasks, zur Verfügung.
Phing wird mit dem PEAR Installer installiert, wie in dieser Befehlszeile zu sehen:
pear channel-discover pear.phing.info
pear install phing/phing
Phing benutzt einfache XML-Dateien, die einen Baum so genannter
Targets definieren, die ihrerseits verschiedene
Tasks ausführen. Einer der von Phing zur Verfügung gestellten Tasks ist
der <phpunit2>-Task. Dieser stellt eine
Portierung des JUnit-Tasks von Apache Ant dar.
Beispiel 12.1 zeigt eine
build.xml-Datei für Phing, die ein
<project> mit dem Namen "BankAccount"
spezifiziert. Das Standard-<target>
dieses Projekts ist "test". Dieses Target führt, unter Verwendung des
<phpunit2>-Tasks, alle Testfälle aus, die
in Quelltext-Dateien gefunden werden, auf die die *Test.php
Bedingung zutrifft. Hierzu wird ein <batchtest>-Element
verwendet, das die Dateien von einem oder mehreren <fileset>-Elementen
aufnimmt. In unserem Beispiel werden die Tests der Klasse
BankAccountTest aus BankAccountTest.php
ausgeführt.
Beispiel 12.1: Phing build.xml Datei für die Tests der BankAccount-Klasse
<?xml version="1.0"?>
<project name="BankAccount" basedir="." default="test">
<target name="test">
<phpunit2 haltonfailure="true" printsummary="true">
<batchtest>
<fileset dir=".">
<include name="*Test.php"/>
</fileset>
</batchtest>
</phpunit2>
</target>
</project>
Der Aufruf von Phing in dem Verzeichnis mit der
build.xml-Datei
(Beispiel 12.1) sowie den
Quelltext-Dateien BankAccount.php
(Beispiel 8.3)
und BankAccountTest.php
(Beispiel 8.1)
wird die Tests ausführen:
phing
Buildfile: /home/sb/build.xml
BankAccount > test:
[phpunit2] Tests run: 4, Failures: 0, Errors: 0, Time elapsed: 0.00067 sec
BUILD FINISHED
Total time: 0.0960 seconds
Tabelle 12.1 zeigt die Parameter für den
<phpunit2>-Task.
Tabelle 12.1. Attribute für das <phpunit2>-Element
| Name | Typ | Beschreibung | Standardwert |
|---|---|---|---|
codecoverage | Boolean | Sammelt Code-Coverage-Informationen. | false |
haltonerror | Boolean | Hält den Phing-Prozess an, wenn bei der Ausführung der Tests ein Fehler auftritt. | false |
haltonfailure | Boolean | Hält den Phing-Prozess an, wenn bei der Ausführung der Tests ein Versager auftritt. Fehler führen ebenfalls zum Abbruch. | false |
printsummary | Boolean | Gibt einzeilige Statistiken für jeden Testfall aus. | false |
Das folgende Beispiel zeigt die Ausgabe des
<phpunit2>-Tasks für einen fehlschlagenden
Test:
phing
Buildfile: /home/sb/build.xml
BankAccount > test:
[phpunit2] Tests run: 4, Failures: 1, Errors: 0, Time elapsed: 0.00067 sec
Execution of target "test" failed for the following reason:
/home/sb/build.xml:5:37: One or more tests failed
BUILD FAILED
/home/sb/build.xml:5:37: One or more tests failed
Total time: 0.0968 seconds
Neben dem notwendigen <batchtest>-Element
erlaubt das <phpunit2>-Element auch
<formatter> als Kindelement. Mit diesem
Element kann die Ausgabe der Testergebnisse in unterschiedlichen
Formaten gesteuert werden. Die Ausgabe erfolgt dabei grundsätzlich in
eine Datei, es sei denn, Sie setzen das Attribut usefile
auf false. Der Name der Ausgabedatei wird durch den
gewählten Formatierer bestimmt und kann durch das Attribut
outfile angepasst werden. Es gibt drei vorgefertigte
Formatierer:
briefGibt detailierte Informationen nur für fehlgeschlagene Tests aus.
plainGibt einzeilige Statistiken für jeden Testfall aus.
xmlGibt die Testergebnisse im XML-Format aus.
Tabelle 12.2 zeigt die
Parameter für den <formatter>-Task.
Tabelle 12.2. Attribute für das <formatter>-Element
| Name | Typ | Beschreibung | Standardwert |
|---|---|---|---|
type | String | Wählt einen vorgefertigten Formatierer (xml, plain, oder brief). | |
classname | String | Name einer eigenen Formatierer-Klasse. | |
usefile | Boolean | Legt fest, ob die Ausgabe in eine Datei erfolgen soll. | true |
todir | String | Verzeichnis, in das die Datei geschrieben werden soll. | |
outfile | String | Name der Ausgabedatei. | Hängt vom gewählten Formatierer ab. |
Um einen Test-Report im HTML-Format zu erstellen, können Sie den
<phpunit2report>-Task verwenden. Dieser
wendet ein XSLT-Stylesheet auf die durch den
<formatter>-Task erzeugte XML-Datei an.
Phing wird mit zwei vorgefertigten XSLT-Stylesheets für die Erstellung
von HTML-Reports (mit oder ohne Frames) ausgeliefert,
phpunit2-frames.xsl und
phpunit2-noframes.xsl.
Beispiel 12.2 zeigt
eine build.xml-Datei für Phing, die die Tests der
Klasse BankAccountTest ausführt und mit Hilfe des
XSLT-Stylesheets phpunit2-frames.xsl einen
Test-Report im HTML-Format erstellt. Die HTML-Dateien werden in das
Verzeichnis report/ geschrieben, das durch das
prepare-<target> erzeugt wird und
über das clean-<target> wieder gelöscht
werden kann.
Beispiel 12.2: Erzeugen eines Test-Reports durch Anwenden eines XSLT-Stylesheets
<?xml version="1.0"?>
<project name="BankAccount" basedir="." default="report">
<target name="prepare">
<mkdir dir="report"/>
</target>
<target name="clean">
<delete dir="report"/>
</target>
<target name="report" depends="prepare">
<phpunit2>
<batchtest>
<fileset dir=".">
<include name="*Test.php"/>
</fileset>
</batchtest>
<formatter type="xml" todir="report" outfile="logfile.xml"/>
</phpunit2>
<phpunit2report infile="report/logfile.xml"
styledir="/usr/lib/php/data/phing/etc"
format="frames"
todir="report"/>
</target>
</project>
Das folgende Beispiel zeigt die Ausgabe des Befehls
phing:
phing
Buildfile: /home/sb/build.xml
BankAccount > prepare:
[mkdir] Created dir: /home/sb/report
BankAccount > report:
BUILD FINISHED
Total time: 0.1112 secondsAbbildung 12.1 zeigt die Startseite des erstellten Test-Reports.
Tabelle 12.3 zeigt
die Parameter des <phpunit2report>-Tasks.
Tabelle 12.3. Attribute für das <phpunit2report>-Element
| Name | Typ | Beschreibung | Standardwert |
|---|---|---|---|
infile | String | Pfad und Name der XML-Datei mit den Testergebnissen. | testsuites.xml |
format | String | Format des zu erstellenden Reports, frames oder noframes. | noframes |
styledir | String | Verzeichnis, in dem die XSLT-Stylesheets liegen. Der Stylesheet für das frames-Format muss phpunit2-frames.xsl heissen, der Stylesheet für das noframes-Format muss phpunit2-noframes.xsl heissen. | |
todir | String | Verzeichnis, in das die Dateien geschrieben werden sollen. |
Analog kann Phing auch Coverage-Reports erstellen. Hierfür benutzen Sie
die <coverage-setup>- und
<coverage-report>-Tasks. Der erste
bereitet eine Datenbank mit den Code-Coverage-Informationen vor. Der
zweite nutzt diese Datenbank und erstellt, wiederum durch Anwendung von
XSLT-Stylesheets, einen Report im HTML-Format.
Beispiel 12.3 zeigt
eine build.xml-Datei für Phing, die die Tests der
Klasse BankAccountTest ausführt und einen
Coverage-Report im HTML-Format erstellt.
Beispiel 12.3: Erzeugen eines Coverage-Reports durch Anwenden eines XSLT-Stylesheets
<?xml version="1.0"?>
<project name="BankAccount" basedir="." default="coverage-report">
<target name="prepare">
<mkdir dir="coverage-report"/>
</target>
<target name="clean">
<delete dir="coverage-report"/>
</target>
<target name="coverage-report" depends="prepare">
<coverage-setup database="./coverage-report/database">
<fileset dir=".">
<include name="*.php"/>
<exclude name="*Test.php"/>
</fileset>
</coverage-setup>
<phpunit2 codecoverage="true">
<batchtest>
<fileset dir=".">
<include name="*Test.php"/>
</fileset>
</batchtest>
</phpunit2>
<coverage-report outfile="coverage-report/coverage.xml">
<report styledir="/usr/lib/php/data/phing/etc"
todir="coverage-report"/>
</coverage-report>
</target>
</project>Abbildung 12.2 zeigt die Startseite des erstellten Coverage-Reports.
PHPUnit ist in einer etwas ungewöhnlichen Weise implementiert worden, und die dabei verwendeten Techniken wären in normalen Anwendungsprogrammen kaum zu pflegen. Es wird Ihnen beim Schreiben von Tests helfen, wenn Sie etwas über diese Art der Implementierung wissen.
Ein einzelner Test wird durch ein
PHPUnit2_Framework_Test-Objekt repräsentiert und
benötigt ein Objekt der Klasse PHPUnit2_Framework_TestResult,
um ausgeführt zu werden. Dieses wird an die run()-Methode
des PHPUnit2_Framework_Test-Objekts übergeben, die
ihrerseits die eigentliche Testmethode ausführt und das Ergebnis im
PHPUnit2_Framework_TestResult-Objekt vermerkt.
Dies ist ein Idiom aus der Welt von Smalltalk, das
Collecting Parameter genannt wird. Es schlägt vor,
dass wenn man Ergebnisse über mehrere Methodenaufrufe hinweg sammeln
möchte, im Falle von PHPUnit die wiederholten Aufrufe der
run()-Methode, man der Methode über einen Parameter
ein Objekt übergeben soll, das die Ergebnisse sammelt. Siehe dazu auch
"JUnit: A Cook's Tour" von Erich Gamma und Kent Beck
[GammaBeck1999] und "Smalltalk Best Practice Patterns"
von Kent Beck [Beck1997].
Damit Sie verstehen, wie PHPUnit die Tests ausführt, betrachten Sie die Testfall-Klasse aus Beispiel 13.1.
Beispiel 13.1: Die Testfall-Klasse EmptyTest
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
class EmptyTest extends PHPUnit2_Framework_TestCase {
private $emptyArray = array();
public function testSize() {
$this->assertEquals(0, sizeof($this->emptyArray));
}
public function testIsEmpty() {
$this->assertTrue(empty($this->emptyArray));
}
}
?>
Wenn dieser Test ausgeführt wird, konvertiert PHPUnit die
Testfall-Klasse zunächst in ein Objekt vom Typ
PHPUnit2_Framework_Test, in diesem Fall in ein
Objekt der Klasse PHPUnit2_Framework_TestSuite.
Dieses enthält zwei Instanzen der Klasse EmptyTest,
wie in Abbildung 13.1
gezeigt.
Beim Ausführen der Testreihe ruft PHPUnit wiederum jeden
EmptyTest einzeln auf. Jeder dieser Tests startet
seine eigene setUp()-Methode, so dass für ihn ein
frisches $emptyArray erzeugt wird, wie in
Abbildung 13.2 zu sehen
ist. Auf diese Weise wird, wenn ein Test das Array verändert, der
andere Test dadurch nicht beeinflusst. Sogar Änderungen an globalen
und super-globalen Variablen (wie $_ENV) in einem
Test haben keine Auswirkungen auf andere Tests.
Kurz gesagt, eine Testfall-Klasse bildet bei ihrer Ausführung einen
Objektbaum mit zwei Ebenen. Jede Testmethode verwendet ihre eigene
Kopie der durch setUp() erzeugten Objekte. Die Folge
davon ist, dass die Tests völlig unabhängig voneinander ausgeführt
werden können.
Um die eigentliche Testmethode auszuführen, sucht PHPUnit mit Hilfe von
Reflection die in $name genannte Methode und ruft sie
auf. In der Smalltalk-Welt bezeichnet man dieses Idiom als
Pluggable Selector. Dieser vereinfacht das
Schreiben von Tests sehr, hat aber einen Nachteil: Man kann nicht mehr
allein durch Betrachten des Codes feststellen, ob eine Methode
aufgerufen wird, sondern muss stattdessen Dateninhalte zur Laufzeit
betrachten.
Für die meisten Anwendungsfälle bietet PHPUnit eine einfache API:
Leiten Sie für Ihre Testfälle eigene Klassen von
PHPUnit2_Framework_TestCase ab, in denen Sie von
Zeit zu Zeit assertTrue() oder
assertEquals() aufrufen. Für alle, die einen
tieferen Einblick in PHPUnit haben möchten, stellen wir nachfolgend
alle veröffentlichten Methoden und Klassen vor.
In der Regel werden Sie bei der Arbeit mit PHPUnit mit fünf Klassen beziehungsweise Schnittstellen zu tun haben:
PHPUnit2_Framework_AssertEine Sammlung von statischen Methoden, mit denen Ergebniswerte mit erwarteten Werten verglichen werden können (Zusicherungen).
PHPUnit2_Framework_TestDie Schnittstelle für alle als Tests dienenden Objekte.
PHPUnit2_Framework_TestCaseEin einzelner Testfall.
PHPUnit2_Framework_TestSuiteEine Testreihe, also eine Sammlung von Testfällen.
PHPUnit2_Framework_TestResultEine Zusammenfassung der Ergebnisse aus der Ausführung von einem oder mehreren Tests.
Abbildung 14.1
zeigt die Beziehungen zwischen den fünf grundlegenden Klassen und
Schnittstellen von PHPUnit:
PHPUnit2_Framework_Assert,
PHPUnit2_Framework_Test,
PHPUnit2_Framework_TestCase,
PHPUnit2_Framework_TestSuite, und
PHPUnit2_Framework_TestResult.
Die meisten für PHPUnit geschriebenen Testfälle sind indirekt von
PHPUnit2_Framework_Assert abgeleitet. Diese Klasse
enthält die Methoden zur automatischen Prüfung von Werten und zur
Meldung von Abweichungen. Diese Methoden sind statisch deklariert, so
dass sie als Zusicherungen im Stile von Design-by-Contract in Ihre
Methoden eingefügt und die Ergebnisse durch PHPUnit ausgewertet werden
können (Beispiel 14.1).
Beispiel 14.1: Zusicherungen im Stile von Design-by-Contract
<?php
require_once 'PHPUnit2/Framework/Assert.php';
class Sample {
public function aSampleMethod($object) {
PHPUnit2_Framework_Assert::assertNotNull($object);
}
}
$sample = new Sample;
$sample->aSampleMethod(NULL);
?>
Fatal error: Uncaught exception 'PHPUnit2_Framework_AssertionFailedError' with message 'expected: <NOT NULL> but was: <NULL>'
Normalerweise werden Sie die Zusicherungen jedoch im Rahmen von Tests verwenden.
Von jeder Zusicherungsmethode gibt es zwei Varianten: Bei der einen dient der letzte Parameter dazu, dass zusammen mit dem Fehler eine Mitteilung angezeigt wird, während die andere keinen solchen Parameter hat. Typischerweise wird diese optionale Meldung bei der Anzeige eines Fehlers mit ausgegeben und erleichtert das Debuggen.
Beispiel 14.2: Verwenden von Zusicherungen mit angepassten Meldungen
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
class MessageTest extends PHPUnit2_Framework_TestCase {
public function testMessage() {
$this->assertTrue(FALSE, 'Dies ist eine angepasste Nachricht.');
}
}
?>
Das folgende Beispiel zeigt die Ausgabe der Ausführung des Tests
testMessage() aus
Beispiel 14.2:
phpunit MessageTest.php
PHPUnit 2.3.0 by Sebastian Bergmann.
F
Time: 0.102507
There was 1 failure:
1) testMessage(MessageTest)
Dies ist eine angepasste Nachricht.
FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0, Incomplete Tests: 0.Tabelle 14.1 zeigt die ganze Vielfalt der Zusicherungen. Die Zusicherungen sind paarweise dargestellt, die zweite Form enthält dabei jeweils den zusätzlichen String-Parameter für den Fehlertext am Ende der Parameterliste.
Tabelle 14.1. Zusicherungen
| Zusicherung | Aktion |
|---|---|
void assertTrue(bool $condition) | Meldet einen Fehler, falls $condition den Wert FALSE hat. |
void assertTrue(bool $condition, string $message) | Meldet einen durch $message bezeichneten Fehler, falls $condition den Wert FALSE hat. |
void assertFalse(bool $condition) | Meldet einen Fehler, falls $condition den Wert TRUE hat. |
void assertFalse(bool $condition, string $message) | Meldet einen durch $message bezeichneten Fehler, falls $condition den Wert TRUE hat. |
void assertNull(mixed $variable) | Meldet einen Fehler, falls $variable nicht den Wert NULL hat. |
void assertNull(mixed $variable, string $message) | Meldet einen durch $message bezeichneten Fehler, falls $variable nicht den Wert NULL hat. |
void assertNotNull(mixed $variable) | Meldet einen Fehler, falls $variable den Wert NULL hat. |
void assertNotNull(mixed $variable, string $message) | Meldet einen durch $message bezeichneten Fehler, falls $variable den Wert NULL hat. |
void assertSame(object $expected, object $actual) | Meldet einen Fehler, falls die beiden Variablen $expected und $actual nicht dasselbe Objekt referenzieren. |
void assertSame(object $expected, object $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Variablen $expected und $actual nicht dasselbe Objekt referenzieren. |
void assertSame(mixed $expected, mixed $actual) | Meldet einen Fehler, falls die beiden Variablen $expected und $actual nicht denselben Typ und Wert haben. |
void assertSame(mixed $expected, mixed $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Variablen $expected und $actual nicht denselben Typ und Wert haben. |
void assertNotSame(object $expected, object $actual) | Meldet einen Fehler, falls die beiden Variablen $expected und $actual dasselbe Objekt referenzieren. |
void assertNotSame(object $expected, object $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Variablen $expected und $actual dasselbe Objekt referenzieren. |
void assertNotSame(mixed $expected, mixed $actual) | Meldet einen Fehler, falls die beiden Variablen $expected und $actual denselben Typ und Wert haben. |
void assertNotSame(mixed $expected, mixed $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Variablen $expected und $actual denselben Typ und Wert haben. |
void assertEquals(array $expected, array $actual) | Meldet einen Fehler, falls die beiden Arrays $expected und $actual nicht identisch sind. |
void assertEquals(array $expected, array $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Arrays $expected und $actual nicht identisch sind. |
void assertNotEquals(array $expected, array $actual) | Meldet einen Fehler, falls die beiden Arrays $expected und $actual identisch sind. |
void assertNotEquals(array $expected, array $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Arrays $expected und $actual identisch sind. |
void assertEquals(float $expected, float $actual, '', float $delta = 0) | Meldet einen Fehler, falls die beiden Gleitpunktzahlen $expected und $actual nicht bis auf $delta gleich sind. |
void assertEquals(float $expected, float $actual, string $message, float $delta = 0) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Gleitpunktzahlen $expected und $actual nicht bis auf $delta gleich sind. |
void assertNotEquals(float $expected, float $actual, '', float $delta = 0) | Meldet einen Fehler, falls die beiden Gleitpunktzahlen $expected und $actual bis auf $delta gleich sind. |
void assertNotEquals(float $expected, float $actual, string $message, float $delta = 0) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Gleitpunktzahlen $expected und $actual bis auf $delta gleich sind. |
void assertEquals(string $expected, string $actual) | Meldet einen Fehler, falls die beiden Zeichenketten $expected und $actual nicht identisch sind. Der Fehler wird als Unterschied der beiden Zeichenketten gemeldet. |
void assertEquals(string $expected, string $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Zeichenketten $expected und $actual nicht identisch sind. Der Fehler wird als Unterschied der beiden Zeichenketten gemeldet. |
void assertNotEquals(string $expected, string $actual) | Meldet einen Fehler, falls die beiden Zeichenketten $expected und $actual identisch sind. |
void assertNotEquals(string $expected, string $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Zeichenketten $expected und $actual identisch sind. |
void assertEquals(mixed $expected, mixed $actual) | Meldet einen Fehler, falls die beiden Variablen $expected und $actual nicht identisch sind. |
void assertEquals(mixed $expected, mixed $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Variablen $expected und $actual nicht identisch sind. |
void assertNotEquals(mixed $expected, mixed $actual) | Meldet einen Fehler, falls die beiden Variablen $expected und $actual identisch sind. |
void assertNotEquals(mixed $expected, mixed $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die beiden Variablen $expected und $actual identisch sind. |
void assertContains(mixed $needle, array $haystack) | Meldet einen Fehler, falls $needle nicht in $haystack enthalten ist. |
void assertContains(mixed $needle, array $haystack, string $message) | Meldet einen durch $message bezeichneten Fehler, falls $needle nicht in $haystack enthalten ist. |
void assertNotContains(mixed $needle, array $haystack) | Meldet einen Fehler, falls $needle in $haystack enthalten ist. |
void assertNotContains(mixed $needle, array $haystack, string $message) | Meldet einen durch $message bezeichneten Fehler, falls $needle in $haystack enthalten ist. |
void assertContains(mixed $needle, Iterator $haystack) | Meldet einen Fehler, falls $needle nicht in $haystack enthalten ist. |
void assertContains(mixed $needle, Iterator $haystack, string $message) | Meldet einen durch $message bezeichneten Fehler, falls $needle nicht in $haystack enthalten ist. |
void assertNotContains(mixed $needle, Iterator $haystack) | Meldet einen Fehler, falls $needle in $haystack enthalten ist. |
void assertNotContains(mixed $needle, Iterator $haystack, string $message) | Meldet einen durch $message bezeichneten Fehler, falls $needle in $haystack enthalten ist. |
void assertRegExp(string $pattern, string $string) | Meldet einen Fehler, falls $string nicht dem Regulären Ausdruck $pattern entspricht. |
void assertRegExp(string $pattern, string $string, string $message) | Meldet einen durch $message bezeichneten Fehler, falls $string nicht dem Regulären Ausdruck $pattern entspricht. |
void assertNotRegExp(string $pattern, string $string) | Meldet einen Fehler, falls $string dem Regulären Ausdruck $pattern entspricht. |
void assertNotRegExp(string $pattern, string $string, string $message) | Meldet einen durch $message bezeichneten Fehler, falls $string dem Regulären Ausdruck $pattern entspricht. |
void assertType(string $expected, mixed $actual) | Meldet einen Fehler, falls die Variable $actual keinen Wert vom Typ $expected enthält. |
void assertType(string $expected, mixed $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die Variable $actual keinen Wert vom Typ $expected enthält. |
void assertNotType(string $expected, mixed $actual) | Meldet einen Fehler, falls die Variable $actual einen Wert vom Typ $expected enthält. |
void assertNotType(string $expected, mixed $actual, string $message) | Meldet einen durch $message bezeichneten Fehler, falls die Variable $actual einen Wert vom Typ $expected enthält. |
Es kann vorkommen, dass Sie neben den hier genannten noch weitere
Zusicherungen benötigen, um projektspezifische Objekte vergleichen zu
können. Schreiben Sie einfach eine Assert-Klasse mit
den Zusicherungen, die Sie zur Vereinfachung Ihrer Tests benötigen.
Bei jedem Scheitern einer Zusicherung wird implizit eine
Flaschenhals-Methode namens fail(string $message)
aufgerufen, die ihrerseits einen
PHPUnit2_Framework_AssertionFailedError auslöst.
Daneben gibt es noch eine parameterlose Variante von
fail(). Sie können fail() auch
explizit aufrufen, wenn Ihr Test auf einen Fehler stößt. Ein Beispiel
hierfür ist der Test auf eine erwartete Ausnahme.
Tabelle 14.2 führt die beiden
Flaschenhals-Methoden in PHPUnit auf.
Tabelle 14.2. Flaschenhals-Methoden
| Methode | Aktion |
|---|---|
void fail() | Meldet einen Fehler. |
void fail(string $message) | Meldet einen durch $message bezeichneten Fehler. |
Die generische Schnittstelle PHPUnit2_Framework_Test
wird von allen Objekten implementiert, die als Tests dienen können.
Eine implementierende Klasse kann einen oder mehrere Tests
repräsentieren. Ihre beiden Methoden sind in
Tabelle 14.3
dargestellt.
Tabelle 14.3. Implementor-Methoden
| Methode | Aktion |
|---|---|
int countTestCases() | Gibt die Anzahl der Tests zurück. |
void run(PHPUnit2_Framework_TestResult $result) | Führt die Tests aus und meldet die Ergebnisse an $result. |
PHPUnit2_Framework_TestCase und
PHPUnit2_Framework_TestSuite sind die beiden
prominentesten Implementierungen von
PHPUnit2_Framework_Test. Sie können
PHPUnit2_Framework_Test aber auch selbst
implementieren. Die Schnittstelle wurde bewusst schmal gehalten, damit
dies möglichst einfach ist.
Ihre Testfall-Klassen leiten Sie von
PHPUnit2_Framework_TestCase ab. In der Regel werden
Sie die Tests aus automatisch erzeugten Testreihen ausführen. In diesem
Fall muss aber jeder Ihrer Tests durch eine Methode repräsentiert sein,
die (konventionsgemäß) mit test* benannt ist.
PHPUnit2_Framework_TestCase implementiert
PHPUnit2_Framework_Test.
countTestCases() liefert 1.
run(PHPUnit2_Framework_TestResult $result) führt erst
setUp(), danach die Testmethode und schließlich
tearDown() aus, wobei alle Ausnahmen in
PHPUnit2_Framework_TestResult gemeldet werden.
Tabelle 14.4 führt das
durch PHPUnit2_Framework_TestCase implementierte
externe Protokoll auf.
Tabelle 14.4. Das externe Protokoll eines Testfalls
| Methode | Aktion |
|---|---|
__construct() | Erzeugt einen Testfall. |
__construct(string $name) | Erzeugt einen Testfall mit dem in $name angegebenen Namen. Der Name wird zur Ausgabe des Testfalls und häufig für den Namen einer durch Reflection auszuführenden Testmethode verwendet. |
string getName() | Gibt den Namen des Testfalls zurück. |
void setName($name) | Setzt den Namen des Testfalls. |
PHPUnit2_Framework_TestResult run(PHPUnit2_Framework_TestResult $result) | Vereinfachte Methode, die den Testfall ausführt und das Ergebnis in $result meldet. |
void runTest() | Überschreiben Sie diese Methode mit einer Testmethode, wenn Sie nicht wollen, dass die Testmethode durch Reflection aufgerufen wird. |
Die beiden Schablonenmethoden setUp() und
tearDown() können Sie überschreiben, um die Objekte
zu erzeugen und zu beseitigen, mit denen Sie die Tests durchführen
wollen.
Tabelle 14.5 zeigt diese
Methoden.
Tabelle 14.5. Schablonenmethoden
| Methode | Aktion |
|---|---|
void setUp() | Überschreiben Sie diese Methode, um die Objekte zu erzeugen, mit denen Sie testen wollen. Jeder Test wird in seinem eigenen Testfall-Objekt ausgeführt, und für jedes wird setUp() separat ausgeführt. |
void tearDown() | Überschreiben Sie diese Methode, um die Objekte zu beseitigen, die nach dem Abschluss des Tests nicht mehr benötigt werden. Im Allgemeinen brauchen Sie in tearDown() nur externe Ressourcen (zum Beispiel Dateien und Sockets) zu beseitigen. |
Eine PHPUnit2_Framework_TestSuite ist ein Kompositum
(Composite) aus
PHPUnit2_Framework_Test-Objekten. Im einfachsten
Fall enthält sie mehrere Testfälle, die alle ausgeführt werden, wenn
auch die Testreihe ausgeführt wird. Als Composite-Objekt kann eine
Testreihe selbst wiederum Testreihen enthalten und so weiter. Dadurch
lassen sich auf einfache Weise Tests aus unterschiedlichen Quellen
kombinieren und gemeinsam ausführen.
Zusätzlich zu dem Protokoll von PHPUnit2_Framework_Test --
run(PHPUnit2_Framework_TestResult $result) und
countTestCases() -- enthält
PHPUnit2_Framework_TestSuite noch ein Protokoll für
die Erzeugung benannter und unbenannter Instanzen.
Tabelle 14.6 zeigt das
Protokoll für die Erzeugung von Instanzen der Klasse
PHPUnit2_Framework_TestSuite.
Tabelle 14.6. Erzeugung von benannten und unbenannten Instanzen
| Methode | Aktion |
|---|---|
__construct() | Liefert eine leere Testreihe. |
__construct(string $theClass) | Liefert eine Testreihe, die für jede Methode der Klasse $theClass eine Instanz dieser Klasse mit dem Namen test* enthält. Existiert keine Klasse mit Namen $theClass, so wird eine leere Testreihe zurückgegeben, die den Namen $theClass trägt. |
__construct(string $theClass, string $name) | Liefert eine Testreihe mit dem angegebenen Namen, die für jede Methode der Klasse $theClass eine Instanz dieser Klasse mit dem Namen test* enthält. |
__construct(ReflectionClass $theClass) | Liefert eine Testreihe, die für jede Methode der Klasse $theClass eine Instanz dieser Klasse mit dem Namen test* enthält. |
__construct(ReflectionClass $theClass, $name) | Liefert eine Testreihe mit dem angegebenen Namen, die für jede Methode der Klasse $theClass eine Instanz dieser Klasse mit dem Namen test* enthält. |
string getName() | Liefert den Namen der Testreihe. |
void setName(string $name) | Setzt den Namen der Testreihe. |
Außerdem enthält die PHPUnit2_Framework_TestSuite
Methoden für das Hinzufügen und das Auslesen von
PHPUnit2_Framework_Tests, die in
Tabelle 14.7 aufgeführt sind.
Tabelle 14.7. Protokoll zum Hinzufügen und Auslesen von Tests
| Methode | Aktion |
|---|---|
void addTest(PHPUnit2_Framework_Test $test) | Fügt der Testreihe einen Test hinzu. |
void addTestFile(string $filename) | Fügt der Testreihe die Tests einer oder mehrerer Testfall-Klassen, die in der angegebenen Quelltext-Datei deklariert sind, hinzu. |
void addTestFiles(array $filenames) | Fügt der Testreihe die Tests der Testfall-Klassen, die in den angegebenen Quelltext-Dateien deklariert sind, hinzu. |
int testCount() | Liefert die Anzahl der Tests, die sich direkt (nicht rekursiv) in dieser Testreihe befinden. |
PHPUnit2_Framework_Test[] tests() | Liefert die Tests, die sich direkt in dieser Testreihe befinden. |
PHPUnit2_Framework_Test testAt(int $index) | Liefert den Test mit dem angegebenen Index. |
Beispiel 14.3 zeigt,
wie Sie eine PHPUnit2_Framework_TestSuite für eine
Testfall-Klasse erzeugen sowie deren Tests ausführen.
Beispiel 14.3: Eine Testreihe erzeugen und ausführen
<?php
require_once 'PHPUnit2/Framework/TestSuite.php';
require_once 'ArrayTest.php';
// Ein TestSuite-Objekt erzeugen, das die Tests
// der Testfall-Klasse ArrayTest enthält.
$suite = new PHPUnit2_Framework_TestSuite('ArrayTest');
// Tests ausführen.
$suite->run();
?>
Als Beispiel für eine hierarchische Komposition von Testreihen betrachten wir die Testreihe von PHPUnit selbst.
Beispiel 14.4 zeigt eine
verkürzte Fassung von Tests/AllTests.php,
Beispiel 14.5 eine
verkürzte Fassung von Tests/Framework/AllTests.php.
Beispiel 14.4: Die Klasse AllTests
<?php
if (!defined('PHPUnit2_MAIN_METHOD')) {
define('PHPUnit2_MAIN_METHOD', 'AllTests::main');
}
require_once 'PHPUnit2/Framework/TestSuite.php';
require_once 'PHPUnit2/TextUI/TestRunner.php';
require_once 'Framework/AllTests.php';
// ...
class AllTests {
public static function main() {
PHPUnit2_TextUI_TestRunner::run(self::suite());
}
public static function suite() {
$suite = new PHPUnit2_Framework_TestSuite('PHPUnit');
$suite->addTest(Framework_AllTests::suite());
// ...
return $suite;
}
}
if (PHPUnit2_MAIN_METHOD == 'AllTests::main') {
AllTests::main();
}
?>
Beispiel 14.5: Die Klasse Framework_AllTests
<?php
if (!defined('PHPUnit2_MAIN_METHOD')) {
define('PHPUnit2_MAIN_METHOD', 'Framework_AllTests::main');
}
require_once 'PHPUnit2/Framework/TestSuite.php';
require_once 'PHPUnit2/TextUI/TestRunner.php';
require_once 'Framework/AssertTest.php';
// ...
class Framework_AllTests {
public static function main() {
PHPUnit2_TextUI_TestRunner::run(self::suite());
}
public static function suite() {
$suite = new PHPUnit2_Framework_TestSuite('PHPUnit Framework');
$suite->addTestSuite('Framework_AssertTest');
// ...
return $suite;
}
}
if (PHPUnit2_MAIN_METHOD == 'Framework_AllTests::main') {
Framework_AllTests::main();
}
?>
Die Klasse Framework_AssertTest ist eine normale
Testfall-Klasse, die sich von
PHPUnit2_Framework_TestCase ableitet.
Das Ausführen von Tests/AllTests.php, zu sehen im
folgenden Beispiel, benutzt den textbasierten Testrunner für die
Ausführung aller Tests, während das Ausführen von
Tests/Framework/AllTests.php nur die Tests für die
PHPUnit2_Framework_* Klassen ausführt.
php AllTests.php
PHPUnit 2.3.0 by Sebastian Bergmann.
.........................................
.........................................
.......
Time: 4.642600
OK (89 tests)
Wenn Sie alle Ihre Tests ausführen, benötigen Sie einen Platz, an dem
die gesamten Ergebnisse abgelegt werden können: wie viele Tests
ausgeführt worden sind, welche davon fehlgeschlagen sind und wie viel
Zeit sie benötigten. PHPUnit2_Framework_TestResult
sammelt die Testresultate. Dabei wird ein einziges
PHPUnit2_Framework_TestResult-Objekt durch den
gesamten Test-Baum gereicht. Wenn ein Test ausgeführt worden oder
gescheitert ist, wird diese Tatsache in
PHPUnit2_Framework_TestResult vermerkt. Am Ende des
Testlaufs PHPUnit2_Framework_TestResult noch eine
Zusammenfassung aller Tests.
Außerdem ist PHPUnit2_Framework_TestResult ein
Objekt, das von anderen Objekten beobachtet werden kann, um den
Fortschritt des Testvorgangs anzuzeigen. So könnte etwa ein
grafischer Testrunner das
PHPUnit2_Framework_TestResult-Objekt verfolgen und
bei jedem Start eines Tests einen Fortschrittsbalken weiterschalten.
Tabelle 14.8 fasst die Methoden zur Auswertung der Versager und Fehler zusammen.
Tabelle 14.8. Die Unterscheidung zwischen Versagern und Fehlern
| Methode | Aktion |
|---|---|
void addError(PHPUnit2_Framework_Test $test, Exception $e) | Vermerkt, dass die Ausführung von $test zur unerwarteten Auslösung der Ausnahme $e geführt hat. |
void addFailure(PHPUnit2_Framework_Test $test, PHPUnit2_Framework_AssertionFailedError $e) | Vermerkt, dass die Ausführung von $test zur unerwarteten Auslösung der Ausnahme $e geführt hat. |
PHPUnit2_Framework_TestFailure[] errors() | Gibt die aufgezeichneten Fehler zurück. |
PHPUnit2_Framework_TestFailure[] failures() | Gibt die aufgezeichneten Versager zurück. |
PHPUnit2_Framework_TestFailure[] notImplemented() | Gibt die aufgezeichneten unvollständigen Tests zurück. |
int errorCount() | Gibt die Anzahl der Fehler zurück. |
int failureCount() | Gibt die Anzahl der Versager zurück. |
int notImplementedCount() | Gibt die Anzahl der unvollständigen Tests zurück. |
int runCount() | Gibt die Gesamtzahl der durchlaufenen Testfälle zurück. |
boolean wasSuccessful() | Gibt zurück, ob alle Tests erfolgreich durchlaufen wurden oder nicht. |
boolean allCompletlyImplemented() | Gibt zurück, ob alle Tests vollständig implementiert waren oder nicht. |
void collectCodeCoverageInformation(bool $flag) | Schaltet das Sammeln von Code-Coverage-Informationen an oder aus. |
array getCodeCoverageInformation() | Gibt die gesammelten Code-Coverage-Informationen zurück. |
Wenn Sie ein Objekt als Beobachter eines
PHPUnit2_Framework_TestResult registrieren möchten,
müssen Sie PHPUnit2_Framework_TestListener
implementieren. Um es anzumelden, rufen Sie die in
Tabelle 14.9
aufgeführte Methode addListener() auf.
Tabelle 14.9. TestResult und TestListener
| Methode | Aktion |
|---|---|
void addListener(PHPUnit2_Framework_TestListener $listener) | Registriert das $listener-Objekt, so dass es informiert wird, wenn Ergebnisse im PHPUnit2_Framework_TestResult aufgezeichnet werden. |
void removeListener(PHPUnit2_Framework_TestListener $listener) | Hebt die Registrierung für das $listener-Objekt auf. |
Ein PHPUnit2_Framework_TestListener-Objekt muss die
in Tabelle 14.10 aufgeführten
Methoden implementieren. Ein Beispiel finden Sie in
Beispiel 15.3.
Tabelle 14.10. Callback-Methoden des TestListener
| Methode | Aktion |
|---|---|
void addError(PHPUnit2_Framework_Test $test, Exception $e) | $test hat die Ausnahme $e ausgelöst. |
void addFailure(PHPUnit2_Framework_Test $test, PHPUnit2_Framework_AssertionFailedError $e) | $test ist fehlgeschlagen, PHPUnit2_Framework_AssertionFailedError beschreibt die verletzte Zusicherung. |
void addIncompleteTest(PHPUnit2_Framework_Test $test, Exception $e) | $test ist unvollständig. |
void startTestSuite(PHPUnit2_Framework_TestSuite $suite) | $suite ist im Begriff zu starten. |
void endTestSuite(PHPUnit2_Framework_TestSuite $suite) | $suite hat ihre Ausführung beendet. |
void startTest(PHPUnit2_Framework_Test $test) | $test ist im Begriff zu starten. |
void endTest(PHPUnit2_Framework_Test $test) | $test hat seine Ausführung beendet. |
Alle oben genannten Klassen befinden sich in
PHPUnit2/Framework. Hier sind alle PHPUnit-Packages:
PHPUnit2/FrameworkDie Basisklassen in PHPUnit.
PHPUnit2/ExtensionsErweiterungen der Basisklassen.
PHPUnit2/RunnerAbstrakte Hilfsklassen für die Ausführung von Tests.
PHPUnit2/TextUIDer textbasierte Testrunner.
PHPUnit2/UtilHilfsklassen, die von den anderen Packages verwendet werden.
Es gibt verschiedene Möglichkeiten, PHPUnit zu erweitern, um das Schreiben von Tests noch einfacher zu machen und verbesserte Rückmeldungen bei der Ausführung von Tests zu erhalten. Im Folgenden wird beschrieben, wie Sie PHPUnit an Ihre speziellen Anforderungen anpassen können.
Fassen Sie Hilfsmethoden in einer abstrakten Kindklasse von
PHPUnit2_Framework_TestCase zusammen, und leiten
Sie Ihre Testfall-Klassen von dieser Klasse ab. Dies ist eine der
einfachsten Möglichkeiten zu einer PHPUnit-Erweiterung.
Sie können Testfälle oder Testreihen in einer Kindklasse von
PHPUnit2_Extensions_TestDecorator einpacken, damit
bestimmte Aktionen vor und nach den Testläufen ausgeführt werden.
PHPUnit bringt zwei fertige Test-Dekorierer mit,
PHPUnit2_Extensions_RepeatedTest und
PHPUnit2_Extensions_TestSetup. Der erste wird
benutzt, um einen Test wiederholt auszuführen und nur dann als Erfolg
zu werten, wenn alle Durchläufe erfolgreich waren. Der zweite wurde in
Kapitel 5 behandelt.
Beispiel 15.1 zeigt
eine verkürzte Fassung des Dekorierers
PHPUnit2_Extensions_RepeatedTest. Hier sehen Sie,
wie Sie einen eigenen Dekorierer schreiben können.
Beispiel 15.1: Der RepeatedTest-Dekorierer
<?php
require_once 'PHPUnit2/Extensions/TestDecorator.php';
class PHPUnit2_Extensions_RepeatedTest extends PHPUnit2_Extensions_TestDecorator {
private $timesRepeat = 1;
public function __construct(PHPUnit2_Framework_Test $test, $timesRepeat = 1) {
parent::__construct($test);
if (is_integer($timesRepeat) &&
$timesRepeat >= 0) {
$this->timesRepeat = $timesRepeat;
}
}
public function countTestCases() {
return $this->timesRepeat * $this->test->countTestCases();
}
public function run($result = NULL) {
if ($result === NULL) {
$result = $this->createResult();
}
for ($i = 0; $i < $this->timesRepeat && !$result->shouldStop(); $i++) {
$this->test->run($result);
}
return $result;
}
}
?>
Die Schnittstelle PHPUnit2_Framework_Test ist klein
und leicht zu implementieren. Dadurch können Sie beispielsweise einen
Test implementieren, der datenbezogene Tests
durchführt.
Beispiel 15.2 zeigt
eine Implementierung von PHPUnit2_Framework_Test für
datenbezogene Tests, bei denen Wertepaare aus einer CSV-Datei
(Comma-Separated Values) verglichen werden. Jede Zeile dieser Datei hat
die Form foo;bar. Der erste Wert ist der erwartete,
der zweite Wert ist der tatsächliche.
Beispiel 15.2: Ein datenbezogener Test
<?php
require_once 'PHPUnit2/Framework/Assert.php';
require_once 'PHPUnit2/Framework/Test.php';
require_once 'PHPUnit2/Framework/TestResult.php';
class DataDrivenTest implements PHPUnit2_Framework_Test {
private $lines;
public function __construct($dataFile) {
$this->lines = file($dataFile);
}
public function countTestCases() {
return sizeof($this->lines);
}
public function run($result = NULL) {
if ($result === NULL) {
$result = new PHPUnit2_Framework_TestResult;
}
$result->startTest($this);
foreach ($this->lines as $line) {
list($expected, $actual) = explode(';', $line);
try {
PHPUnit2_Framework_Assert::assertEquals(trim($expected), trim($actual));
}
catch (PHPUnit2_Framework_ComparisonFailure $e) {
$result->addFailure($this, $e);
}
catch (Exception $e) {
$result->addError($this, $e);
}
}
$result->endTest($this);
return $result;
}
}
$test = new DataDrivenTest('data_file.csv');
$result = $test->run();
$failures = $result->failures();
print $failures[0]->thrownException()->toString();
?>
expected: <foo> but was: <bar>
Indem Sie der Methode run() ein spezialisiertes
PHPUnit2_Framework_TestResult-Objekt übergeben,
können Sie zusätzliche Informationen über die laufenden Tests
bekommen.
Sie müssen nicht unbedingt eine komplette Kindklasse von
PHPUnit2_Framework_TestResult schreiben. Alternativ
können Sie auch einen neuen PHPUnit2_Framework_TestListener
implementieren (siehe Tabelle 14.10),
den Sie vor der Testausführung bei dem
PHPUnit2_Framework_TestResult registrieren.
Beispiel 15.3
zeigt eine einfache Implementierung der Schnittstelle
PHPUnit2_Framework_TestListener.
Beispiel 15.3: Ein einfacher TestListener
<?php
require_once 'PHPUnit2/Framework/TestListener.php';
class SimpleTestListener implements PHPUnit2_Framework_TestListener {
public function addError(PHPUnit2_Framework_Test $test, Exception $e) {
printf(
"Bei der Ausführung des Tests '%s' ist ein Fehler aufgetreten.\n",
$test->getName()
);
}
public function addFailure(PHPUnit2_Framework_Test $test,
PHPUnit2_Framework_AssertionFailedError $e) {
printf(
"Test '%s' fehlgeschlagen.\n",
$test->getName()
);
}
public function addIncompleteTest(PHPUnit2_Framework_Test $test, Exception $e) {
printf(
"Test '%s' ist unvollständig.\n",
$test->getName()
);
}
public function startTest(PHPUnit2_Framework_Test $test) {
printf(
"Test '%s' gestartet.\n",
$test->getName()
);
}
public function endTest(PHPUnit2_Framework_Test $test) {
printf(
"Test '%s' beendet.\n",
$test->getName()
);
}
public function startTestSuite(PHPUnit2_Framework_TestSuite $suite) {
printf(
"TestSuite '%s' gestartet.\n",
$suite->getName()
);
}
public function endTestSuite(PHPUnit2_Framework_TestSuite $suite) {
printf(
"TestSuite '%s' beendet.\n",
$suite->getName()
);
}
}
?>
Beispiel 15.4
zeigt, wie Sie Testreihe ausführen und diese Ausführung mit einem
SimpleTestListener-Objekt beobachten.
Beispiel 15.4: Ausführen und Beobachten einer Testreihe
<?php
require_once 'PHPUnit2/Framework/TestResult.php';
require_once 'PHPUnit2/Framework/TestSuite.php';
require_once 'ArrayTest.php';
require_once 'SimpleTestListener.php';
// PHPUnit2_Framework_TestSuite-Objekt erzeugen,
// das die Tests aus ArrayTest enthält.
$suite = new PHPUnit2_Framework_TestSuite('ArrayTest');
// PHPUnit2_Framework_TestResult-Objekt erzeugen und
// ein SimpleTestListener-Objekt als Beobachter registrieren.
$result = new PHPUnit2_Framework_TestResult;
$result->addListener(new SimpleTestListener);
// Tests ausführen.
$suite->run($result);
?>
TestSuite 'ArrayTest' gestartet. Test 'testNewArrayIsEmpty' gestartet. Test 'testNewArrayIsEmpty' beendet. Test 'testArrayContainsAnElement' gestartet. Test 'testArrayContainsAnElement' beendet. TestSuite 'ArrayTest' beendet.
Um andere Rückmeldungen aus der Testausführung zu erhalten, können
Sie einen eigenen Testrunner schreiben. Die abstrakte Basisklasse
PHPUnit2_Runner_BaseTestRunner, die auch von
PHPUnit2_TextUI_TestRunner (dem textbasierten
Testrunner) genutzt wird, kann Ihnen dabei als Ausgangspunkt dienen.
Es gibt eine Versionsreihe von PHPUnit, die auf PHP 4 ausgelegt ist und nicht auf PHP 5 angewiesen ist. Auf Grund des eingeschränkten Objektmodells von PHP 4 ist PHPUnit für PHP 4 im Gegensatz zu PHPUnit für PHP 5 keine vollständige Portierung von JUnit. Darüber hinaus fehlen einige Leistungsmerkmale wie beispielsweise die Code-Coverage-Analyse.
Die folgende Befehlszeile zeigt die Installation von PHPUnit für PHP 4 mit dem PEAR Installer:
pear install -f http://pear.phpunit.de/get/PHPUnit-1.3.3.tgz
Eine Testklasse, die mit PHPUnit für PHP 4 verwendet werden kann, sieht
eine Testklasse, die mit PHPUnit für PHP 5 verwendet werden kann, ähnlich.
Der wichtigste Unterschied ist, dass sich die Testklasse von
PHPUnit_TestCase ableitet. Diese Klasse ist ihrerseits
eine Kindklasse der Klasse PHPUnit_Assert, die die
Zusicherungsmethoden zur Verfügung stellt.
Beispiel A.1
zeigt eine Fassung der Testklasse ArrayTest, die mit
PHPUnit für PHP 4 verwendet werden kann.
Beispiel A.1: Eine Testklasse für PHPUnit 1.x schreiben
<?php
require_once 'PHPUnit/TestCase.php';
class ArrayTest extends PHPUnit_TestCase {
var $_fixture;
function setUp() {
$this->_fixture = array();
}
function testNewArrayIsEmpty() {
$this->assertEquals(0, sizeof($this->_fixture));
}
function testArrayContainsAnElement() {
$this->_fixture[] = 'Element';
$this->assertEquals(1, sizeof($this->_fixture));
}
}
?>
PHPUnit für PHP 4 verfügt über kein Kommandozeilen-Werkzeug. Die Ausführung der Tests erfolgt üblicherweise durch Schreiben einer Testsuite "von Hand", wie in Beispiel A.2 gezeigt.
Beispiel A.2: Tests mit PHPUnit 1.x ausführen
<?php
require_once 'ArrayTest.php';
require_once 'PHPUnit.php';
$suite = new PHPUnit_TestSuite('ArrayTest');
$result = PHPUnit::run($suite);
print $result->toString();
?>
TestCase arraytest->testnewarrayisempty() passed TestCase arraytest->testarraycontainsanelement() passed
Abbildung A.1 zeigt das einzige Leistungsmermal, das PHPUnit for PHP 5 noch nicht bietet: ein grafisches Werkzeug für die Testausführung auf Basis von PHP-GTK.
[Beck2002] Test Driven Development by Example. Copyright © 2002. Addison-Wesley. ISBN 0-321-14653-0.
[Beck2004] JUnit Pocket Guide. Quick Lookup and Advice. Copyright © 2004. O'Reilly Media. ISBN 0-596-00743-4.
[Bergmann2005] Professionelle Softwareentwicklung mit PHP 5. Objektorientierung, Entwurfsmuster, Modellierung, Fortgeschrittene Datenbankprogrammierung. Copyright © 2005. dpunkt.verlag. ISBN 3-89864-229-1.
Copyright (c) 2005-2009 Sebastian Bergmann.
Dieses Werk ist lizensiert unter der Creative Commons Namensnennung 2.0
Lizenz.
Um eine Kopie dieser Lizenz einzusehen, besuchen Sie bitte
http://creativecommons.org/licenses/by/2.0/ oder schreiben Sie einen
Brief an Creative Commons, 559 Nathan Abbott Way, Stanford,
California 94305, USA.
Nachstehend ist eine Zusammenfassung der Lizenz, gefolgt von ihrem
vollständigen Text.
--------------------------------------------------------------------
Sie dürfen:
* den Inhalt vervielfältigen, verbreiten und öffentlich aufführen
* Bearbeitungen anfertigen
* den Inhalt kommerziell nutzen
Zu den folgenden Bedingungen:
Namensnennung. Sie müssen den Namen des Autors/Rechtsinhabers nennen.
* Im Falle einer Verbreitung müssen Sie anderen die Lizenzbedingungen,
unter die dieser Inhalt fällt, mitteilen.
* Jede dieser Bedingungen kann nach schriftlicher Einwilligung des
Rechtsinhabers aufgehoben werden.
Die gesetzlichen Schranken des Urheberrechts bleiben hiervon unberührt.
Dies war eine Zusammenfassung des Lizenzvertrags in allgemeinverständlicher
Sprache.
====================================================================
Creative Commons Legal Code
Namensnennung 2.0
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.
Lizenzvertrag
DAS URHEBERRECHTLICH GESCHÜTZTE WERK ODER DER SONSTIGE SCHUTZGEGENSTAND
(WIE UNTEN BESCHRIEBEN) WIRD UNTER DEN BEDINGUNGEN DIESER
CREATIVE COMMONS PUBLIC LICENSE ("CCPL" ODER "LIZENZVERTRAG") ZUR
VERFÜGUNG GESTELLT. DER SCHUTZGEGENSTAND IST DURCH DAS URHEBERRECHT
UND/ODER EINSCHLÄGIGE GESETZE GESCHÜTZT.
DURCH DIE AUSÜBUNG EINES DURCH DIESEN LIZENZVERTRAG GEWÄHRTEN RECHTS AN
DEM SCHUTZGEGENSTAND ERKLÄREN SIE SICH MIT DEN LIZENZBEDINGUNGEN
RECHTSVERBINDLICH EINVERSTANDEN. DER LIZENZGEBER RÄUMT IHNEN DIE HIER
BESCHRIEBENEN RECHTE UNTER DER VORAUSSETZUNGEIN, DASS SIE SICH MIT
DIESEN VERTRAGSBEDINGUNGEN EINVERSTANDEN ERKLÄREN.
1. Definitionen
a. Unter einer "Bearbeitung" wird eine Übersetzung oder andere
Bearbeitung des Werkes verstanden, die Ihre persönliche geistige
Schöpfung ist. Eine freie Benutzung des Werkes wird nicht als
Bearbeitung angesehen.
b. Unter den "Lizenzelementen" werden die folgenden Lizenzcharakteristika
verstanden, die vom Lizenzgeber ausgewählt und in der Bezeichnung der
Lizenz genannt werden: "Namensnennung", "Nicht-kommerziell",
"Weitergabe unter gleichen Bedingungen".
c. Unter dem "Lizenzgeber" wird die natürliche oder juristische Person
verstanden, die den Schutzgegenstand unter den Bedingungen dieser
Lizenz anbietet.
d. Unter einem "Sammelwerk" wird eine Sammlung von Werken, Daten oder
anderen unabhängigen Elementen verstanden, die aufgrund der Auswahl
oder Anordnung der Elemente eine persönliche geistige Schöpfung ist.
Darunter fallen auch solche Sammelwerke, deren Elemente systematisch
oder methodisch angeordnet und einzeln mit Hilfe elektronischer Mittel
oder auf andere Weise zugänglich sind (Datenbankwerke). Ein Sammelwerk
wird im Zusammenhang mit dieser Lizenz nicht als Bearbeitung
(wie oben beschrieben) angesehen.
e. Mit "Sie" und "Ihnen" ist die natürliche oder juristische Person
gemeint, die die durch diese Lizenz gewährten Nutzungsrechte ausübt
und die zuvor die Bedingungen dieser Lizenz im Hinblick auf das Werk
nicht verletzt hat, oder die die ausdrückliche Erlaubnis des Lizenzgebers
erhalten hat, die durch diese Lizenz gewährten Nutzungsrechte trotz
einer vorherigen Verletzung auszuüben.
f. Unter dem "Schutzgegenstand" wird das Werk oder Sammelwerk oder das
Schutzobjekt eines verwandten Schutzrechts, das Ihnen unter den
Bedingungen dieser Lizenz angeboten wird, verstanden.
g. Unter dem "Urheber" wird die natürliche Person verstanden, die das
Werk geschaffen hat.
h. Unter einem "verwandten Schutzrecht" wird das Recht an einem anderen
urheberrechtlichen Schutzgegenstand als einem Werk verstanden, zum
Beispiel einer wissenschaftlichen Ausgabe, einem nachgelassenen Werk,
einem Lichtbild, einer Datenbank, einem Tonträger, einer Funksendung,
einem Laufbild oder einer Darbietung eines ausübenden Künstlers.
i. Unter dem "Werk" wird eine persönliche geistige Schöpfung verstanden,
die Ihnen unter den Bedingungen dieser Lizenz angeboten wird.
2. Schranken des Urheberrechts. Diese Lizenz lässt sämtliche Befugnisse
unberührt, die sich aus den Schranken des Urheberrechts, aus dem
Erschöpfungsgrundsatz oder anderen Beschränkungen der
Ausschließlichkeitsrechte des Rechtsinhabers ergeben.
3. Lizenzierung. Unter den Bedingungen dieses Lizenzvertrages räumt Ihnen der
Lizenzgeber ein lizenzgebührenfreies, räumlich und zeitlich (für die Dauer
des Urheberrechts oder verwandten Schutzrechts) unbeschränktes einfaches
Nutzungsrecht ein, den Schutzgegenstand in der folgenden Art und Weise
zu nutzen:
a. den Schutzgegenstand in körperlicher Form zu verwerten, insbesondere
zu vervielfältigen, zu verbreiten und auszustellen;
b. den Schutzgegenstand in unkörperlicher Form öffentlich wiederzugeben,
insbesondere vorzutragen, aufzuführen und vorzuführen, öffentlich
zugänglich zu machen, zu senden, durch Bild- und Tonträger wiederzugeben
sowie Funksendungen und öffentliche Zugänglichmachungen wiederzugeben;
c. den Schutzgegenstand auf Bild- oder Tonträger aufzunehmen, Lichtbilder
davon herzustellen, weiterzusenden und in dem in a. und b. genannten
Umfang zu verwerten;
d. den Schutzgegenstand zu bearbeiten oder in anderer Weise umzugestalten
und die Bearbeitungen zu veröffentlichen und in dem in a. bis c.
genannten Umfang zu verwerten;
Die genannten Nutzungsrechte können für alle bekannten Nutzungsarten ausgeübt
werden. Die genannten Nutzungsrechte beinhalten das Recht, solche
Veränderungen an dem Werk vorzunehmen, die technisch erforderlich sind,
um die Nutzungsrechte für alle Nutzungsarten wahrzunehmen. Insbesondere sind
davon die Anpassung an andere Medien und auf andere Dateiformate umfasst.
4. Beschränkungen. Die Einräumung der Nutzungsrechte gemäß Ziffer 3 erfolgt
ausdrücklich nur unter den folgenden Bedingungen:
a. Sie dürfen den Schutzgegenstand ausschließlich unter den Bedingungen
dieser Lizenz vervielfältigen, verbreiten oder öffentlich wiedergeben,
und Sie müssen stets eine Kopie oder die vollständige Internetadresse
in Form des Uniform-Resource-Identifier (URI) dieser Lizenz beifügen,
wenn Sie den Schutzgegenstandvervielfältigen, verbreiten oder
öffentlich wiedergeben. Sie dürfen keine Vertragsbedingungen anbieten
oder fordern, die die Bedingungen dieser Lizenz oder die durch sie
gewährten Rechte ändern oder beschränken. Sie dürfen den
Schutzgegenstand nicht unterlizenzieren. Sie müssen alle Hinweise
unverändert lassen, die auf diese Lizenz und den Haftungsausschluss
hinweisen. Sie dürfen den Schutzgegenstand mit keinen technischen
Schutzmaßnahmen versehen, die den Zugang oder den Gebrauch des
Schutzgegenstandes in einer Weise kontrollieren, die mit den
Bedingungen dieser Lizenz im Widerspruch stehen. Die genannten
Beschränkungen gelten auch für den Fall, dass der Schutzgegenstand
einen Bestandteil eines Sammelwerkes bildet; sie verlangen aber
nicht, dass das Sammelwerk insgesamt zum Gegenstand dieser Lizenz
gemacht wird. Wenn Sie ein Sammelwerk erstellen, müssen Sie -
soweit dies praktikabel ist - auf die Mitteilung eines Lizenzgebers
oder Urhebers hin aus dem Sammelwerk jeglichen Hinweis auf diesen
Lizenzgeber oder diesen Urheber entfernen. Wenn Sie den
Schutzgegenstand bearbeiten, müssen Sie - soweit dies praktikabel ist -
auf die Aufforderung eines Rechtsinhabers hin von der Bearbeitung
jeglichen Hinweis auf diesen Rechtsinhaber entfernen.
b. Wenn Sie den Schutzgegenstand oder eine Bearbeitung oder ein
Sammelwerk vervielfältigen, verbreiten oder öffentlich wiedergeben,
müssen Sie alle Urhebervermerke für den Schutzgegenstand unverändert
lassen und die Urheberschaft oder Rechtsinhaberschaft in einer der
von Ihnen vorgenommenen Nutzung angemessenen Form anerkennen, indem
Sie den Namen (oder das Pseudonym, falls ein solches verwendet wird)
des Urhebers oder Rechteinhabers nennen, wenn dieser angegeben ist.
Dies gilt auch für den Titel des Schutzgegenstandes, wenn dieser
angeben ist, sowie - in einem vernünftigerweise durchführbaren Umfang -
für die mit dem Schutzgegenstand zu verbindende Internetadresse in
Form des Uniform-Resource-Identifier (URI), wie sie der Lizenzgeber
angegeben hat, sofern dies geschehen ist, es sei denn, diese
Internetadresse verweist nicht auf den Urhebervermerk oder die
Lizenzinformationen zu dem Schutzgegenstand. Bei einer Bearbeitung
ist ein Hinweis darauf aufzuführen, in welcher Form der
Schutzgegenstand in die Bearbeitung eingegangen ist
(z.B. "Französische Übersetzung des ... (Werk) durch ... (Urheber)"
oder "Das Drehbuch beruht auf dem Werk des ... (Urheber)"). Ein
solcher Hinweis kann in jeder angemessenen Weise erfolgen, wobei
jedoch bei einer Bearbeitung, einer Datenbank oder einem Sammelwerk
der Hinweis zumindest an gleicher Stelle und in ebenso auffälliger
Weise zu erfolgen hat wie vergleichbare Hinweise auf andere
Rechtsinhaber.
c. Obwohl die gemäss Ziffer 3 gewährten Nutzungsrechte in umfassender
Weise ausgeübt werden dürfen, findet diese Erlaubnis ihre
gesetzliche Grenze in den Persönlichkeitsrechten der Urheber und
ausübenden Künstler, deren berechtigte geistige und persönliche
Interessen bzw. deren Ansehen oder Ruf nicht dadurch gefährdet
werden dürfen, dass ein Schutzgegenstand über das gesetzlich
zulässige Maß hinaus beeinträchtigt wird.
5. Gewährleistung. Sofern dies von den Vertragsparteien nicht anderweitig
schriftlich vereinbart,, bietet der Lizenzgeber keine Gewährleistung
für die erteilten Rechte, außer für den Fall, dass Mängel arglistig
verschwiegen wurden. Für Mängel anderer Art, insbesondere bei der
mangelhaften Lieferung von Verkörperungen des Schutzgegenstandes,
richtet sich die Gewährleistung nach der Regelung, die die Person,
die Ihnen den Schutzgegenstand zur Verfügung stellt, mit Ihnen
außerhalb dieser Lizenz vereinbart, oder - wenn eine solche Regelung
nicht getroffen wurde - nach den gesetzlichen Vorschriften.
6. Haftung. Über die in Ziffer 5 genannte Gewährleistung hinaus haftet
Ihnen der Lizenzgeber nur für Vorsatz und grobe Fahrlässigkeit.
7. Vertragsende
a. Dieser Lizenzvertrag und die durch ihn eingeräumten Nutzungsrechte
enden automatisch bei jeder Verletzung der Vertragsbedingungen
durch Sie. Für natürliche und juristische Personen, die von Ihnen
eine Bearbeitung, eine Datenbank oder ein Sammelwerk unter diesen
Lizenzbedingungen erhalten haben, gilt die Lizenz jedoch weiter,
vorausgesetzt, diese natürlichen oder juristischen Personen erfüllen
sämtliche Vertragsbedingungen. Die Ziffern 1, 2, 5, 6, 7 und 8
gelten bei einer Vertragsbeendigung fort.
b. Unter den oben genannten Bedingungen erfolgt die Lizenz auf
unbegrenzte Zeit (für die Dauer des Schutzrechts). Dennoch behält
sich der Lizenzgeber das Recht vor, den Schutzgegenstand unter
anderen Lizenzbedingungen zu nutzen oder die eigene Weitergabe des
Schutzgegenstandes jederzeit zu beenden, vorausgesetzt, dass solche
Handlungen nicht dem Widerruf dieser Lizenz dienen (oder jeder
anderen Lizenzierung, die auf Grundlage dieser Lizenz erfolgt ist
oder erfolgen muss) und diese Lizenz wirksam bleibt, bis Sie
unter den oben genannten Voraussetzungen endet.
8. Schlussbestimmungen
a. Jedes Mal, wenn Sie den Schutzgegenstand vervielfältigen, verbreiten
oder öffentlich wiedergeben, bietet der Lizenzgeber dem Erwerber
eine Lizenz für den Schutzgegenstand unter denselben
Vertragsbedingungen an, unter denen er Ihnen die Lizenz eingeräumt
hat.
b. Jedes Mal, wenn Sie eine Bearbeitung vervielfältigen, verbreiten
oder öffentlich wiedergeben, bietet der Lizenzgeber dem Erwerber
eine Lizenz für den ursprünglichen Schutzgegenstand unter denselben
Vertragsbedingungen an, unter denen er Ihnen die Lizenz eingeräumt
hat.
c. Sollte eine Bestimmung dieses Lizenzvertrages unwirksam sein, so
wird die Wirksamkeit der übrigen Lizenzbestimmungen dadurch nicht
berührt, und an die Stelle der unwirksamen Bestimmung tritt eine
Ersatzregelung, die dem mit der unwirksamen Bestimmung angestrebten
Zweck am nächsten kommt.
d. Nichts soll dahingehend ausgelegt werden, dass auf eine Bestimmung
dieses Lizenzvertrages verzichtet oder einer Vertragsverletzung
zugestimmt wird, so lange ein solcher Verzicht oder eine solche
Zustimmung nicht schriftlich vorliegen und von der verzichtenden
oder zustimmenden Vertragspartei unterschrieben sind.
e. Dieser Lizenzvertrag stellt die vollständige Vereinbarung zwischen
den Vertragsparteien hinsichtlich des Schutzgegenstandes dar. Es
gibt keine weiteren ergänzenden Vereinbarungen oder mündlichen
Abreden im Hinblick auf den Schutzgegenstand. Der Lizenzgeber ist
an keine zusätzlichen Abreden gebunden, die aus irgendeiner Absprache
mit Ihnen entstehen könnten. Der Lizenzvertrag kann nicht ohne eine
übereinstimmende schriftliche Vereinbarung zwischen dem Lizenzgeber
und Ihnen abgeändert werden.
f. Auf diesen Lizenzvertrag findet das Recht der Bundesrepublik
Deutschland Anwendung.
CREATIVE COMMONS IST KEINE VERTRAGSPARTEI DIESES LIZENZVERTRAGES UND
ÜBERNIMMT KEINERLEI GEWÄHRLEISTUNG FÜR DAS WERK. CREATIVE COMMONS IST
IHNEN ODER DRITTEN GEGENÜBER NICHT HAFTBAR FÜR SCHÄDEN JEDWEDER ART.
UNGEACHTET DER VORSTEHENDEN ZWEI (2) SÄTZE HAT CREATIVE COMMONS ALLE
RECHTE UND PFLICHTEN EINES LIZENSGEBERS, WENN SICH CREATIVE COMMONS
AUSDRÜCKLICH ALS LIZENZGEBER BEZEICHNET.
AUSSER FÜR DEN BESCHRÄNKTEN ZWECK EINES HINWEISES AN DIE ÖFFENTLICHKEIT,
DASS DAS WERK UNTER DER CCPL LIZENSIERT WIRD, DARF KENIE VERTRAGSPARTEI
DIE MARKE "CREATIVE COMMONS" ODER EINE ÄHNLICHE MARKE ODER DAS LOGO VON
CREATIVE COMMONS OHNE VORHERIGE GENEHMIGUNG VON CREATIVE COMMONS NUTZEN.
JEDE GESTATTETE NUTZUNG HAT IN ÜBREEINSTIMMUNG MIT DEN JEWEILS GÜLTIGEN
NUTZUNGSBEDINGUNGEN FÜR MARKEN VON CREATIVE COMMONS ZU ERFOLGEN, WIE SIE
AUF DER WEBSITE ODER IN ANDERER WEISE AUF ANFRAGE VON ZEIT ZU ZEIT
ZUGÄNGLICH GEMACHT WERDEN.
CREATIVE COMMONS KANN UNTER http://creativecommons.org KONTAKTIERT WERDEN.
====================================================================