23
Mär

Modultests mit PHPUnit

categories Computer, PHP, Tutorials    

Eine kleine private Exkursion in die Tiefen einer OpenSource-Gemeinde hat mir mal wieder vor Augen gehalten, wie man sich das Coder-Dasein durch die Verwendung von Modultests („UnitTests“) vereinfachen kann. Nicht jeder benutzt sie, jeder sollte. In der PHP-Programmierung ist die ideale Software hierfür PHPUnit.

Die Software wird im GitHub verteilt, ist gut dokumentiert, weit verbreitet (guckt mal in viele größere Klassen rein, man stolpert oft über den einen oder anderen Testordner) und genießt hohes Ansehen bei Entwicklern. Die Software ist auch im PEAR und kann darüber installiert werden (ein paar Dependencies gibt es für zusätzliche Funktionen).

An einem einfachen Beispiel möchte ich die grundsätzliche Verfahrensweise demonstrieren. Angenommen, in einem PHP-Projekt gibt es irgendwo eine Funktion, welche den Ostersonntag eines Jahres bestimmt. Der Einfachheit halber nennen wir sie easter($jahr). So eine Funktion könnte beispielsweise eingebunden sein, wenn es um die Errechnung von verschiedenen Feiertage geht – somit könnte sie eine gewisse Kritikalität im Projekt innehaben. Wie testet man nun so eine Funktion mit PHPUnit?

Ich steige hier mal mitten in PHPUnit ein:

require_once 'includes/functions.php';
 
class EasterTest extends PHPUnit_Framework_TestCase {
  public function testEasterOk() {
    $easter2011 = easter(2011);
    $this->assertEquals($easter2011, gmmktime(0,0,0,4,24,2011));
  }
 
  public function testEasterFalse() {
    $easter2011 = easter(2011);
    $this->assertNotEquals($easter2011, gmmktime(0,0,0,12,24,2011));
  }
} 

Zuerst habe einen Verweis auf das PHP-Script (hier: functions.php), in welcher die Funktion easter($Jahr) enthalten ist. Dann generiere ich eine Klasse („EasterTest“), welche PHPUnit erweitert. In dieser Klasse sind zwei Tests abgelegt:

Jetzt kommt der trockene theoretische Teil…
Hier geht es nicht etwa darum, das Ergebnis zu finden – das hat man ohnehin schneller mit einem Blick auf dem Kalender. Vielmehr ist es Ziel, das Ergebnis der Funktion mit einem definierten „Wunschergebnis“ zu vergleichen. Ich weiß, dass der 24.04. dieses Jahr der Ostersonntag ist… und ich weiß, dass es der 24.12. definitiv nicht ist.

PHPUnit – wie auch fast alle andere Software in diesem Umfeld – arbeitet mit „Assertions“ (Behauptungen). Es gibt eine Sammlung von einfachen Funktionen, welche solche Assertions in verschiedenen Konstrukten ermöglichen – angefangen von Vergleichsoperationen wie die im Beispiel verwendeten assertEquals und assertNotEquals über False/True-Varianten, Array-Prüfern und einigem anderen.

Ist dies viel Arbeit?
Bitte urteilt selbst.. ein einfacher Test ist fix geschrieben. Nicht schwer, oder?

Zurück zur Praxis.
Wie kann man nun die Tests auswerten? Am einfachsten über die Shell. Und hier ist es nun wirklich egal, ob man sich in einer Dosbox, Bash oder wo auch immer aufhält.

D:\htdocs\>phpunit EasterTest.php
PHPUnit 3.5.11 by Sebastian Bergmann.

..
 
Time: 0 seconds, Memory: 3.75Mb
 
OK (2 tests, 2 assertions)

PHPUnit kann auch andere Output-Formate, beispielsweise das TAP-Format („Test Anything Protocol“), JSON, XML und andere.

D:\htdocs\>phpunit --tap EasterTest.php
TAP version 13
ok 1 - EasterTest::testEasterOk
ok 2 - EasterTest::testEasterFalse
1..2

Wenn ich nun in einem Test einen Fehler habe, sieht das so aus (ich habe hierfür den Test 1 so verändert, dass die Behauptung lautet, der 23.04.2011 wäre Ostersonntag – ist aber in Wirklichkeit der Samstag davor):

D:\htdocs\>phpunit --tap EasterTest.php
TAP version 13
not ok 1 - Failure: EasterTest::testEasterOk
  ---
  message: 'Failed asserting that <integer:1303516800> matches expected <integer:1303603200>.'
  severity: fail
  data:
    got: 1303516800
    expected: 1303603200
  ...
ok 2 - EasterTest::testEasterFalse
1..2

Wenn ich nun – wann und warum auch immer – an der eigentlichen Funktion wieder mal arbeite, dann habe ich weiterhin meine gespeicherten Tests. Wenn diese mir also Mist zurückgeben, ist entweder mein Test falsch (eher unwahrscheinlich), oder meine veränderte Funktion ist nicht ok… und genau darum geht es hier. Modultests wie dieser hier machen den Code deutlich stabiler und nachträgliche Arbeiten, Erweiterungen etc. erheblich einfacher.

Und wenn es mal Bugs gibt… schreibt einen Test und fixt dann den Bug!

8
Mär

qTranslate

categories Computer    

Ich teste gerade qTranslate. Mal sehen, wie das funktioniert!

5
Mär

PHP-Commandline-Parameter mit Console_Getopt

categories PHP, Tutorials    

Aus der Historie heraus verwenden die meisten Entwickler PHP hauptsächlich als serverseitige Skriptsprache zur Erstellung von Webseiten oder Webanwendungen. Mit PHP kann man jedoch noch viel mehr machen: es bietet ein Commandline-Interface, welches ermöglicht, auch unter verschiedenen OS Shell-Scripts zu schreiben und zu verwenden.

Um übergebene Parameter auszuwerten, bedient man sich der dafür reservierten Variable $argv.

Beispiel 1: test_argv.php

<?php
var_dump($argv);
?>

Ausgabe von Beispiel 1:

d:\php5> php.exe test_argv.php IchBinParam1 UndIch2
array(3) {
  [0]=>
  string(13) "test_argv.php"
  [1]=>
  string(12) "IchBinParam1"
  [2]=>
  string(7) "UndIch2"
}

So weit, so gut. Wenn es jetzt darum geht, die Argumente zu parsen und zu prüfen, kann so ein Konstrukt rund um $argv schnell recht aufwändig werden. Um dies zu erleichtern, gibt es im PEAR-Paket eine kleine Klasse namens Console_Getopt.

Installation
Falls man PEAR verwendet, kann man die Klasse sehr einfach darüber installieren.

d:\php5> pear install Console_Getopt-1.3.0

Man ist jedoch nicht darauf angewiesen. Es reicht vollkommen aus, die Klasse von der offiziellen PEAR-Seite zu downloaden und in das Projekt einzubinden.

Anwendung
Die Anwendung ist denkbar einfach. Man bindet die Klasse in das eigene Programm ein, initialisiert das Klassenobjekt, definiert erlaubte Optionen und prüft dann die übergebenen Parameter.

Hier ein einfaches Beispiel zur Anwendung.

Beispiel 2: demo_getopt.php

<?php
// Klasse einbinden
include ("Getopt.php");

// Klasse initialisieren
$cg = new Console_Getopt();

/** 
 * Erlaubte Optionen:
 * -a Abba
 * -b Beatles
 * -q Queen
 */
$allowedOptions = "abq";

// Kommandozeile lesen
$args = $cg->readPHPArgv();

// Optionen herausfiltern
$ret = $cg->getopt($args, $allowedOptions);

// Errorhinweis
if (PEAR::isError($ret)) {
    die ("Fehler in Kommandozeile: " . $ret->getMessage() . "\n");
}

// Ausgabe der Optionen
print_r($ret);
?> 

Ausgabe zu Beispiel 2:

d:\temp\getopt>php.exe demo_getopt.php -ab Who Genesis
Array
(
    [0] => Array
        (
            [0] => Array
                (
                    [0] => a
                    [1] =>
                )

            [1] => Array
                (
                    [0] => b
                    [1] =>
                )

        )

    [1] => Array
        (
            [0] => Who
            [1] => Genesis
        )

)

d:\temp\getopt>d:\php5\php.exe demo_getopt.php -c
Error in command line: Console_Getopt: unrecognized option -- c

Nach dem Filtern der Optionen kann man nun die über die Kommandozeile eingebenen Parameter auswerten und weiter verarbeiten.

Beispiel 3: demo_getopt_erweitert.php

.
.
.
// Optionen herausfiltern
$ret = $cg->getopt($args, $allowedOptions);

$optionen = $ret[0];
if (sizeof($optionen) > 0) {
    // mindestens eine Option wurde gewählt
    foreach ($optionen as $o) {
        switch ($o[0]) {
            case 'a':
                echo "gewählte Band: Abba\n"; 
                break;
            case 'b':
                echo "gewählte Band: Beatles\n"; 
                break;
            case 'q':
                echo "gewählte Band: Queen\n"; 
                break;
        }
    }
} 

Die Klasse kann noch mehr, unter anderem ist es z.B. möglich, notwendige und optionale Parameter definieren. Auch kann man definieren, ob Parameter Zeichenketten oder Zahlenwerte sein dürfen.

Wer einen tieferen Einblick in die Klasse möchte, die Klasse selbst ist gut dokumentiert. Einen ausführlichen und guten Artikel mit weiteren Anwendungsbeispielen gibt es hier bei Zend.