Impressum · Kontakt · Hilfe
Besucher online · Mitglieder



Portal > Foren > Ankündigungen, News und Feedback > Tutorials > [PHP] PHPUnit oder testgetriebene Entwicklung

Layoutprobleme? - Styleswitcher!

Antwort
 
Themen-Optionen
Alt 30.08.2007, 16:34 Nach oben    #1
mepeisen
Martin Eisengardt
 
Registriert seit: 30.03.2006
Ort: Pfinztal
Beiträge: 350
Standard [PHP] PHPUnit oder testgetriebene Entwicklung

Ich beziehe mich mit diesem Tutorial auf PHPUnit in der Version 3.1.2.

Ich gehe auch einmal davon aus, dass PHPUnit zum Beispiel via Pear installiert wurde und im PHP-Include-Pfad auf welche Weise auch immer zur Verfügung steht. Die Installation ist auch sehr gut beschrieben.

----------
Das Schreiben von Testfällen ist auch entsprechend beschrieben, so dass ich hier nur oberflächlich darauf eingehen möchte. Aus der Anleitung ergibt sich beispielsweise folgendes Script:
PHP-Code:
<?php
require_once 'PHPUnit/Framework.php';
 
class 
ArrayTest extends PHPUnit_Framework_TestCase
{
    public function 
testNewArrayIsEmpty()
    {
        
// Create the Array fixture.
        
$fixture = array();
 
        
// Assert that the size of the Array fixture is 0.
        
$this->assertEquals(0sizeof($fixture));
    }
 
    public function 
testArrayContainsAnElement()
    {
        
// Create the Array fixture.
        
$fixture = array();
 
        
// Add an element to the Array fixture.
        
$fixture[] = 'Element';
 
        
// Assert that the size of the Array fixture is 1.
        
$this->assertEquals(1sizeof($fixture));
    }
}
?>
Dieser Test lässt sich relativ leicht über die Kommandozeile starten:
Code:
phpunit ArrayTest
Voraussetzung ist, dass man das PHP-Verzeichnis im Pfad hat, da dort durch pear bei der Installation ein kleines Batch-Script hinterlegt wird. Man kann sich aber auch sehr schnell und leicht ein PHP-Script schreiben, was den Test selbst startet:
PHP-Code:
require_once 'PHPUnit/Framework.php';
require_once 
'PHPUnit/TextUI/TestRunner.php';
PHPUnit_TextUI_TestRunner::run(myTestSuite());
 
function 
myTestSuite()
{
    
$suite = new PHPUnit_Framework_TestSuite('class ArrayTest');
    
$suite->addTest(new ArrayTest('testNewArrayIsEmpty'));
    
$suite->addTest(new ArrayTest('testArrayContainsAnElement'));
    return 
$suite;

Bevor ihr nun weiterlest, probiert dieses einfache Beispiel einmal aus. Installiert also PHPUnit und schreibt (z.B. durch Copy/Paste) einen solchen ArrayTest. Führt ihn anschliessend aus via Kommandozeile und auch durch ein solches Test-Script. Es sollte ein Ergebnis, wie in der Anleitung angegeben, erscheinen.
Code:
PHPUnit 3.2.0 by Sebastian Bergmann.
 
..
 
Time: 0 seconds
 
 
OK (2 tests)
Punkte bedeuten erfolgreich. Alles andere ist fehlgeschlagen. Im übrigen sieht PHPUnit auch Warnungen (unintialized Variables etc.) als Fehler an, was auch gut so ist. Voraussetzung ist, dass in der PHP-Ini das error_reporting auf E_ALL gesetzt wurde oder am Anfang des Scriptes auf E_ALL gesetzt wird.

Im Test selber schreibt man sich eine Art Plan. Was will ich testen? In obigem Beispiel wird das Array getestet (Methode testNewArrayIsEmpty). Ich lege also ein Array an und erwarte, dass es von Anfang an leer ist. Solche Erwartungshaltungen werden "Asserts" genannt. Das ist aus vielen Programmiersprachen bekannt. Im Grunde bewirkt das Assert in PHPUnit nur soviel: Wenn die Erwartung (0 und sizeof sind gleich) nicht eintrifft, schlägt der Testfall fehl.

Wenn der Testfall fehlschlägt, kann man zwei Ursachen ausmachen:
a) Das Array ist nicht leer
b) Die Funktion sizeof hat einen Fehler.
Streng genommen müssen wir die Funktion sizeof auch in einem PHPUnit-Test prüfen. Sie soll gefälligst richtig arbeiten. Nur so können wir sicher sein, dass unser Testfall richtig funktioniert. Glücklicherweise machen das nur paranoide Entwickler. Wir gehen davon aus, dass der Basisumfang des PHP auch korrekt funktioniert. Schliesslich gibt es vom Zend-Team auch irgendwelche Testscripte, die genau das sicherstellen.

Aber wenn ihr aufgepasst habt, wird euch das Vorgehen oder die Denkweise klar, die man beim Schreiben von Testfällen an den Tag legen sollte. Man kann auch in einer Testmethode mehrere Asserts hineinschreiben.

----------
Doch was bringt einem das Schreiben der Testfälle?

Oft ist es so, dass man eine PHP- Klasse schreibt und erst wenn man fertig ist, geht man hin, dass man sie entweder in eine Anwendung einbaut und dann live testet oder dass man Testscripte für diese Klasse schreiben will. Genau diese Reihenfolge bringt eigentlich relativ wenig. Sicherlich erreicht man irgendwann durch die geschriebenen PHPUnit-Tests eine hohe Abdeckung der Tests. Aus meiner Praxiserfahrung kann ich das Schreiben von Tests nur begrüßen. Auch im Nachhinein ist das immer zu empfehlen. Selbst wenn man nicht alles abdecken kann oder es zeitlich nicht schafft, jede Klasse mit Testfällen abzudecken, hilft es. Die PHPUnit-Tests, die man einmal geschrieben hat, kann man bei Veränderungen immer wieder ablaufen lassen. So kann man immer wieder schauen, ob man etwas kaputt gemacht hat und ob alle Teile der Anwendung noch funktionieren.

Doch richtig interessant wird die testgetriebene Entwicklung. Was bedeutet das? Im Prinzip wird hier die Reihenfolge geändert, in der man vorgeht.
  • Schritt 1: Man überlegt sich, wie eine Klasse oder ein Script aussehen soll. Optional kann ein Design per UML gemacht werden. In jedem Fall weiss man aber bereits vorab in Prosa, was die Klasse oder das Script tut. Die Methoden sind definiert und ihre Arbeitsweise bekannt. In diesem Schritt macht man also idealerweise das gleiche wie bisher: Man plant die Umsetzung.
  • Schritt 2: Man schreibt die Testfälle. Bereits jetzt überlegt man sich, was man von den Methoden einer Klasse erwartet. Was soll passieren, welche Fälle sollen sie Abdecken und ganz wichtig: Welche Fehlerfälle, Exceptions sollen abgedeckt werden. Also immer überlegen: Was passiert im Positiv-Fall, was im Negativ-Fall.
  • Schritt 3: Erwartungsgemäß laufen alle Testfälle schief. Wir haben hier bei unserer Klasse noch keinerlei Zeile Code geschrieben! Die Klasse tut also erwartungsgemäß noch nicht das, was sie soll.
  • Schritt 4: Erst jetzt wird die Klasse implementiert. Die Methoden werden ausformuliert. Hat man sie fertig, werden die PHPUnit-Tests, die ja bereits in Schritt 3 fertiggestellt wurden, gestartet und wenn einer schief läuft hat man entweder im PHP-Unit Test etwas falsch gemacht (auch das sollte man nie ausschliessen, daher auch lieber viele kleine Tests statt einem großen) oder in der Klasse.
Aber ist das nicht ein Mehraufwand?

Das typische Argument gegen Testscripte und automatisierte Tests ist der Aufwand. Und scheinbar will man keine Tests machen, da eh keine Zeit ist. Sicherlich, es gibt Fälle, in denen man mit Spatzen auf Kanonen schießt (Ich liebe diese meine Eigenkration). Wenn man ein einfaches HelloWorld Script, was nur per Echo etwas ausgibt, per PHPUnit testen will, sollte man es gleich lassen.

Bei etwas aufwändigeren Projekten und Problemen hat man zunächst den Aufwand durch strukturierte Programmierung und durch die Testfälle, die man vor allem schreibt. Es ist also nicht mehr das schnelle Erfolgserlebnis da. Es braucht auch ein bischen Übung, bis man wirklich flott die Tests hinbekommt. Aber bei fast jedem praktischen Beispiel, egal ob PHP, Java oder C++, egal ob Hobby oder Profi, zeigt sich, dass man die Zeit bereits bei der Realisierung spart, denn übliche aufwendige Tests und Fehlersuche bleiben meist aus. Mir persönlich geht es so, dass beim Schreiben der Testfälle bereits im Hinterkopf die Klasse entsteht, die ich da testen will. Ich programmiere deutlich zielbewusster und damit qualitativ hochwertiger. Ich programmiere, durch die Testfälle getrieben, deutlich strukturierter und hacke meist nur noch herunter, weil ich mit Schritt 2 bereits im Hinterkopf die Klasse und die Code-Zeilen vorbereitet habe. Also hole ich die vermeindlich verlorene Zeit wieder auf. Und dass es sich fast immer rechnet, wird jeder bestätigen, der einmal intensiv nach diesem Schema entwickelt hat.

Doch betrachten wir komplexere Konstellationen im Detail, beispielsweise meine Plugin-Verwaltung, wo ich aktuell dran sitze. Es geht um eine Verwaltung von Plugins. Jedes Plugin enthält einige Informationen, wie beispielsweise Source-Verzeichnisse, Autor, Dokumentation, Name, Versionsnummer usw. Ich habe eine klare API geschrieben, also Klassen und Methoden und ich weiß, was sie im einzelnen machen sollen (Schritt 1). Ich schreibe nun für alles Testfälle (Schritt 2). Ich schreibe und implementiere die Klassen (Schritt 4) und merke mir alle Informationen in einem größeren Array-Geflecht. Zunächst klappt auch alles wunderbar.
Nun will ich ein neues Feature einbauen, wo ich die ganzen Informationen auf der Festplatte cache und bei Bedarf neu einlese. Also erweitere ich meine API (Schritt 1) und schreibe einige neue Testfälle (Schritt 2). Glücklicherweise muss ja nach mit Cache alles so funktionieren, wie ohne Cache auch. Ich kann also viele Tests von früher kopieren bzw. muss sie einmal mit deaktiviertem Cache und einmal mit aktiviertem Cache aufrufen. Ich implementiere die Klasse und lasse die Testfälle laufen (Schritt 4).
Plötzlich schlagen viele Testfälle fehl, weil das mit dem Cache wohl doch nicht so klappt wie gewünscht (Programmierfehler in Schritt 4). Nach langen Versuchen und einer aufreibenden Fehlersuche entschließe ich mich, die interne Array-Struktur rauszuwerfen und durch Objekte abzulösen. Irgendwann laufen alle meine Testfälle und ich bin begeistert.

Die Umsetzung des Problems hat mich 10 Stunden gekostet. Ohne Testfälle und Bugs hätte ich vielleicht 9 Stunden gebraucht. Mit diesen Bugs wäre ich ohne Testfälle nie so schnell fertig geworden, weil ich vor dem Problem stand, dass der Cache nicht funktionierte und ich daraufhin umgebaut hatte. Ich hätte nie so schnell relativ sicher sein können, dass nach dem Umbau alles wie gewünscht funktioniert...

Was ist die Moral von der Geschichte?

Die größte Zeitersparnis entsteht bei Umbauten oder beim Einbau neuer Features, denn hier passieren oft die nervigsten Fehler. Oft passiert es hier, dass ein Entwickler sagt "Das muss doch gehen, da habe ich nichts verändert.". Und dagegen kann man nichts tun. Hat man aber sauber gearbeitet und haufenweise Testscripte geschrieben, die so gut wie alle Konstellationen abdecken, fällt sowas dann sofort auf und nicht erst Wochen später, wenn der Kunde meckert...

Eines sollte ich noch erwähnen: PHPUnit ersetzt nicht den Test, den man selbst durchführt, also das Ausprobieren einer Webseite. Aber PHPUnit ist ein nützliches Hilfsmittel, um dauerhaft und nachwirkend eine höhere Qualität zu erreichen.

----------
Zusätzliche Kniffe:

Hat man XDebug installiert und aktiviert, kann man mit PHPUnit noch einige sehr interessante Features nutzen. So kann man automatisch einen HTML-Report generieren. Das Verzeichnis dazu kann man dem Test-Runner übergeben.
PHP-Code:
PHPUnit_TextUI_TestRunner::run(myTestSuite(), array('reportDirectory' => irname(__FILE__).'/test-report/')); 
Der Clou: XDebug erlaubt es nun, dass PHPUnit herausfindet, welche Zeilen des PHPCode durchlaufen wurden und welche nicht. Code-Coverage nennt man das. Der Vorteil: Es lässt sich so sehen, ob man weitere Testfälle aufnehmen muss, um wirklich alles testen zu können. Wenn nämlich eine Code-Zeile nicht ausgeführt wurde, wird sie ja nie durch die Tests abgedeckt.

Man darf sich jedoch im Umkehrschluss nicht darauf verlassen, dass eine hundert prozentige Abdeckung der Code-Zeilen automatisch bedeutet, dass man alle Konstellationen abdeckt. Und zudem tauchen die Scripte, die überhaupt nicht geladen wurden, auch nicht im Report auf.
mepeisen ist offline  
Add Post to del.icio.usBookmark Post in TechnoratiDiesen Beitrag zu Mister Wong hinzufügen!
Mit Zitat antworten
Alt 06.12.2007, 11:56 Nach oben    #2
Ben
Benjamin Klaile
 
Benutzerbild von Ben
 
Registriert seit: 02.12.2004
Ort: Remagen
Beiträge: 3.812
Standard

Als Ergänzug sei an dieser Stelle noch auf einen Artikel in der Zend Developer Zone verwiesen.
Ben ist offline  
Add Post to del.icio.usBookmark Post in TechnoratiDiesen Beitrag zu Mister Wong hinzufügen!
Mit Zitat antworten
Antwort

« [PHP] Zufallspasswort - Generator | [PHP] thumbnails erstellen - kleine Funktion »

Aktive Benutzer in diesem Thema: 1 (Registrierte Benutzer: 0, Gäste: 1)
 
Themen-Optionen

Forumregeln
Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge anzufügen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

vB Code ist An.
Smileys sind An.
[IMG] Code ist An.
HTML-Code ist Aus.
Trackbacks are An
Pingbacks are An
Refbacks are Aus

Ähnliche Themen
Thema Autor Forum Antworten Letzter Beitrag
PHPUnit - Kostenloses Buch zum Open-Source-Tool robo47 Literatur 4 27.02.2007 14:36
Entwicklung des Flash Players 9 für Linux Ben Nachrichten 0 26.07.2006 16:22
PHPUnit wird von PEAR abgesplittet Ben Nachrichten 0 15.07.2006 14:59


Alle Zeitangaben in WEZ +2. Es ist jetzt 07:11 Uhr.

Nach oben
Wir nutzen das Zend Framework, vBulletin (vBulletin v3.6.7, Copyright ©2000-2008, Jelsoft Enterprises Ltd.
SEO by vBSEO 3.0.0) und vBSEO.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44