PHPUnit - Eine kurze Einführung

7

Click here to load reader

description

Ein kurze Einführung. PHP Usergroup Hannover, 2.11.2006

Transcript of PHPUnit - Eine kurze Einführung

Page 1: PHPUnit - Eine kurze Einführung

PHPUnit

Eine kurze Einführung vom Frank Staude <[email protected]>

vorgetragen beim Treffen der

PHP Usergroup Hannover am 02.11.2006

Warum Testen?

Jeder von uns, der programmiert, macht auch Fehler. Es gibt zwar Anwendungsbereiche in der Luft- und Raumfahrt, wo die Fehlerfreiheit von Programmen mathematisch bewiesen werden muss, aber auch dies ist noch kein Garant für das Funktionieren eines gesamten Systems. Die Mars-Sonde „Climate-Orbiter“1 der NASA zum Beispiel verglühte in der Marsatmosphäre, weil zwei Subsysteme zur Steuerung in verschiedenen Maßeinheiten arbeiteten und die Einheit nicht Bestandteil der Schnittstellenspezifikation war. Schaden: über 100 Millionen US-Dollar. Dies zeigt auch, dass, wenn eine Software an sich fehlerfrei ist, das System als Ganzes trotzdem versagen kann, weil die Umgebung der Software fehlerhaft ist. Für uns als PHP-Programmierer bedeutet dies: Auch wenn unser Programm fehlerfrei ist, kann die Anwendung als Ganzes Abstürzen, weil in einer bestimmten Situation ein Fehler in PHP selbst, im verwendeten Webserver oder auch im Betriebssystem auftritt. Der Anwender gibt uns die Schuld. Wir müssen mit viel Aufwand versuchen, die Situation nachzustellen, in der dieser Fehler auftritt und – wenn wir feststellen, dass der Fehler durch die Umgebung verursacht wird –, wie wir ihn umgehen können. Da mathematische Korrektheitsbeweise für uns in der Regel nicht in Frage kommen, bleiben nur Tests, um sicherzustellen, dass ein Programm fehlerfrei im Sinne der Spezifikation ist. Damit Tests auch wirklich eingesetzt werden, müssen sie

• leicht zu erlernen sein. Wir sind PHP Programmierer, wir wollen nicht erst eine weitere Scriptsprache eines Testtools lernen.

• leicht zu schreiben sein.

Wenn ein Test nicht leicht zu schreiben ist, werden wir ihn nicht schreiben.

• leicht zu lesen sein. Der Testcode sollte nichts überflüssiges eEnthalten (müssen).

• leicht auszuführen sein. Ein Klick auf einen Button oder ein Aufruf an der Kommandozeile – und dann sollen Ergebnisse kommen.

1 http://de.wikipedia.org/wiki/Mars_Climate_Orbiter

Page 2: PHPUnit - Eine kurze Einführung

• schnell auszuführen sein. Die Tests sollen so schnell sein, dass man sie viele, viele Male am Tag ausführen kann.

• isoliert sein. Tests dürfen sich nicht gegenseitig beeinflussen. Auch bei veränderter Reihenfolge der Aufrufe müssen die Ergebnisse gleich sein.

• kombinierbar sein. Jede beliebige Anzahl sowie Kombinationen von Tests können zusammen durchgeführt werden.

Da nach jeder noch so kleinen Änderung in einem großen Projekt nicht ein Programmierer alles manuell testen kann, müssen die Tests automatisiert sein.

Automatische Tests

Genau genommen ist ein automatischer Test nichts weiter als ein Vergleich zwischen einem berechneten Ergebnis und einem vorab berechneten Wert. Wenn beide identisch sind, war der Test erfolgreich, andernfalls nicht. Solch ein automatisierter Test lässt sich jederzeit ohne Mehraufwand wiederholen und zeigt keine Ermüdungserscheinungen, übersieht nicht mal etwas aus versehen. Und mit jedem Durchlauf gewinnt man mehr Vertrauen in den eigenen Code. Die Idee der automatischen Tests stammt von den so genannten agilen Methoden2 zur Softwareentwicklung wie z.B. Extreme Programming (XP)3. Sehr interessant ist auch der Ansatz der testgetriebenen Entwicklung4, bei der zu erst der Test geschrieben wird und erst dann der Code. Das heißt, dass man mit dem Programmieren fertig ist, wenn der Test fehlerfrei durchläuft. Diese Art der Arbeit zwingt einem zu sehr zielgerichteten Vorgehen. Man muss sich von Anfang an mehr auf die Schnittstellen konzentrieren und schreibt bei der Implementierung noch so viel Code, wie nötig ist, damit der Test fehlerfrei durchlaufen wird.

PHPUnit

PHPUnit2 ist die Vollständige Umsetzung von JUnit 3.8.1 nach PHP5. Sebastian Bergmann hat es während seines Informatikstudiums entwickelt. PHPUnit2 ist die aktuelle, unter PHP5 lauffähige Version. PHPUnit ist die Version für PHP4, sie kann einige Dinge nicht und wird auch nicht mehr weiterentwickelt. Nur kurz angerissen, ohne im Rahmen dieses Vortrags weiter darauf einzugehen, die folgenden Features von PHPUnit2:

� Unterstützung unvollständiger Tests � Erstellen der Testdokumentation � Erstellen von Testcode-Skeletten (Skeletons) � Analyse der Codeabdeckung (mit xdebug) � Profiling (Performancetest)

2 http://de.wikipedia.org/wiki/Agile_Softwareentwicklung 3 http://de.wikipedia.org/wiki/Extreme_Programming 4 http://de.wikipedia.org/wiki/Testgetriebene_Entwicklung

Page 3: PHPUnit - Eine kurze Einführung

� Protokollierung der Testergebnisse als XML, TAP oder GraphViz

Installation

PHPUnit ist Bestandteil von PEAR und daher unter Linux und Windows gleichartig zu installieren. Anwender des beliebten XAMPPs Paketes haben alles, was sie benötigen, schon dabei. >pear version PEAR Version: 1.4.5 PHP Version: 5.1.6 Zend Engine Version: 2.1.0 Running on: Windows NT MUNCH 5.1 build 2600 >pear install phpunit2 Skipping package "pear/PHPUnit2", already installed as version 2.3.5 No valid packages found install failed

Unit Tests

Mit Unit Tests werden Klassen oder Teile einer Anwendung getestet, aber nicht die komplette Anwendung. Durch das Ausführen der Tests an der Kommandozeile beispielsweise die GET und POST Parameter – ebenso wie superglobale Variablen oder andere Umgebungsvariablen, die der Webserver zur Verfügung stellt. Zum Testen der ganzen Anwendung und des Interfaces im Browser sei hier auf Selenium5 verwiesen (bzw. die Einführung im Linux Magazin6 ). PHPUnit wird derzeit auch aufgebohrt7, so dass man auch Selenium-Tests mit PHPUnit realisieren kann. Es gibt zwei Arten, die Tests zu organisieren. Entweder schreibt man die Tests zusammen mit dem zu testenden Code in eine Datei oder man schreibt die Tests in separate Dateien. Schreibt man die Tests mit in den Code, so ist man in der Lage, auch nicht-öffentliche Methoden zu testen, auf die man von außen nicht zugreifen kann. Meiner Meinung nach sollten sich die Tests aber auf die Schnittstelle konzentrieren und nicht die Implementierungsdetails testen. Außerdem kann bei PHP nicht per Präprozessor-Anweisung der Testcode weggelassen werden, so dass auch die ausgelieferte Anwendung immer den Testcode „mitschleppt“. Und durch Trennung von Test- und Produktionscode hat man zusätzlich noch das gute Gefühl, dass der Test „nicht angefasst wurde“ wenn man Refactorings8 vornimmt.

5 http://www.openqa.org/selenium/ 6 http://www.linux-magazin.de/Artikel/ausgabe/2006/10/perl/perl.html 7 http://sebastian-bergmann.de/archives/631-Integrating-PHPUnit-with-Selenium.html 8 http://de.wikipedia.org/wiki/Refactoring

Page 4: PHPUnit - Eine kurze Einführung

Ein erster Test

Angenommen, wir haben eine kleine Klasse UnitDemo, in der eine Methode isString() implementiert ist. Diese wollen wir nun testen. <?php class UnitDemo { function isString($s) { return (is_string($s)); } } ?>

Dazu benötigen wir einen Test. <?php require_once("PHPUnit2/Framework/TestCase.php"); require_once("UnitDemo.php"); class UnitDemoTest extends PHPUnit2_Framework_TestCase { public function testIsString() { $this->assertTrue(UnitDemo::isString("test")); } } ?>

Die Ausführung des Tests liefert das folgende Ergebnis phpunit UnitDemoTest.php PHPUnit 2.3.5 by Sebastian Bergmann. . Time: 0.033367 OK (1 test)

Schauen wir uns den Test mal genauer an: require_once("PHPUnit2/Framework/TestCase.php");

require_once("UnitDemo.php");

Von der TestCase-Klasse werden die Tests abgeleitet. Unsere zu testende Klasse muss natürlich auch bekannt sein, class UnitDemoTest extends PHPUnit2_Framework_TestCase

erstellt eine neue, von PHPUnit2_Framework_TestCase abgeleitete Klasse. In dieser Klasse werden die einzelnen Testes als Methoden implementiert.

Page 5: PHPUnit - Eine kurze Einführung

$this->assertTrue(UnitDemo::isString("test"));

assertTrue (und auch das Gegenstück assertFalse) sind Tests auf Zusicherung (assertion) eines Wertes. Das heißt, das Beispiel sichert zu, dass der Aufruf der Methode isString mit dem Parameter „test“ ein True liefert. Dem Testergebnis wird für jeden Test der Status zurückgebeben:

� Ein Punkt bedeutet OK, so wie in obigen Beispiel. F wird ausgegeben, wenn eine Zusicherung verletzt wurde.

� E wird ausgegeben, wenn bei dem Test ein Fehler aufgetreten ist und � I, wenn der Test als unvollständig markiert wurde oder noch nicht Implementiert

wurde. Wenn wir also den Test erweitern um eine falsche Zusicherung: <?php require_once("PHPUnit2/Framework/TestCase.php"); require_once("UnitDemo.php"); class UnitDemoTest extends PHPUnit2_Framework_TestCase { public function testIsString() { $this->assertTrue(UnitDemo::isString("test")); } public function testIsStringWithNumber() { $this->assertFalse(UnitDemo::isString("13")); } } ?>

erhalten wir im Test wie erwartet eine Fehlermeldung: phpunit UnitDemoTest.php PHPUnit 2.3.5 by Sebastian Bergmann. .F Time: 0.000689 There was 1 failure: 1) testIsStringWithNumber(UnitDemoTest) C:\Dokumente und Einstellungen\Frank Staude\unit\UnitDemoTest.php:15 FAILURES!!! Tests run: 2, Failures: 1, Errors: 0, Incomplete Tests: 0.

Der zweite Test ist wie erwartet fehlgeschlagen. Die beiden AssertTrue/-False Funktionen haben übrigens noch einen weiteren Parameter: Einen optionalen Text, der ausgegeben wird, wenn die Zusicherung fehlschlägt. Bei umfangreichen Tests kann es sinnvoll sein, hier eine

Page 6: PHPUnit - Eine kurze Einführung

Erläuterung zu geben, welche Zusicherung fehlgeschlagen ist – so kann man der Teststatistik bereits entnehmen, wo etwas schiefgelaufen ist.

Zusicherungen

Die folgenden Zusicherungen gibt es: assertTrue(Boolean $condition) Meldet einen Fehler, falls $condition

den Wert FALSE hat. assertFalse(Boolean $condition) Meldet einen Fehler, falls $condition

den Wert TRUE hat. assertNull(Mixed $variable) Meldet einen Fehler, falls $variable

nicht den Wert NULL hat. assertNotNull(Mixed $variable) Meldet einen Fehler, falls $variable den

Wert NULL hat. assertSame(Object $expected, Object

$actual) Meldet einen Fehler, falls die beiden Variablen $expected und $actual nicht dasselbe Objekt referenzieren.

assertNotSame(Object $expected, Object

$actual) Meldet einen Fehler, falls die beiden Variablen $expected und $actual dasselbe Objekt referenzieren.

assertEquals(Array|Float|String|Mixed

$expected, Array|Float|String|Mixed

$actual)

Meldet einen Fehler, falls die beiden Array|Float|String|Mixed

$expected und $actual nicht identisch sind.

assertNotEquals(Array|Float|String|Mixed

$expected, Array|Float|String|Mixed

$actual)

Meldet einen Fehler, falls die beiden Array|Float|String|Mixed

$expected und $actual identisch sind. assertContains(Mixed $needle, Array

$haystack) Meldet einen Fehler, falls $needle nicht in $haystack enthalten ist.

assertNotContains(Mixed $needle, Array

$haystack) Meldet einen Fehler, falls $needle in $haystack enthalten ist.

assertRegExp(String $pattern, String

$string) Meldet einen Fehler, falls $string nicht dem Regulären Ausdruck $pattern entspricht.

assertNotRegExp(String $pattern, String

$string) Meldet einen Fehler, falls $string dem Regulären Ausdruck $pattern entspricht.

assertType(String $expected, Mixed

$actual) Meldet einen Fehler, falls die Variable $actual keinen Wert vom Typ $expected enthält.

assertNotType(String $expected, Mixed

$actual) Meldet einen Fehler, falls die Variable $actual einen Wert vom Typ $expected enthält.

Jede der aufgeführten Methoden hat noch einen weiteren, optionalen Parameter für den Hinweistext. Desweiteren kann man auch noch auf exceptions testen, mit setUp() und tearDown() seine Testumgebung beeinflussen und, wie am Anfang beschrieben, Testskelette erzeugen lassen

Page 7: PHPUnit - Eine kurze Einführung

und CodeCoverage und Laufzeitanalyse machen – all dies würde aber den Rahmen einer „kleinen“ Einführung sprengen. Der Interessierte sei hier an das frei verfügbare PHPUnit

kurz & gut verwiesen, welches unter http://www.phpunit.de/pocket_guide/index.de.php im Internet zu finden ist bzw. von O’Reilly in gedruckter Fassung vorliegt.

Literatur & Links

� Homepage PHPUnit http://www.phpunit.de/

� PHPUnit kurz & gut http://www.phpunit.de/pocket_guide/index.de.php

� Homepage von Sebastian Bergmann http://sebastian-bergmann.de/

� Professionelle Softwareentwicklung mit PHP 5 http://www.professionelle-softwareentwicklung-mit-php5.de/erste_auflage/

Der Autor

1970 geboren. Gründer und geschäftsführender Gesellschafter der TRILOS® IT-Dienstleistungen GbR9. Anschrift: TRILOS IT-Dienstleistungen Frank Staude Am Rathaus 15 30952 Ronnenberg

Tel: (05 11) 21 44 98 - 60 Fax: (05 11) 21 44 98 - 65 E-Mail: [email protected] Webseite: http://www.trilos.de PGP-Fingerprint: 1504 336A D8ED A2FD F189 996D DBBE 5A79 A9F2 E6C0

9 http://www.trilos.de