Ökonomie in der agilen Softwareentwicklung

Seit ein paar Wochen beschäftige ich mich intensiv mit ASP und C# von Microsoft. In meiner Karriere als „Software-Projekt-Mensch“ habe ich bereits viele Projekte mit PHP und Java umgesetzt. Mir ist früher schon aufgefallen, dass der Overhead beim Entwickeln in einer Entwicklungsumgebung wie Visual Studio oder Eclipse wesentlich größer ist, als wenn ich schnell ein paar PHP Skripte mit Notepad++ in einem LAMP Stack hacke.

Dieses mal habe ich aber mal einen gut vergleichbaren Fall, da ich ein Web System mit PHP bereits vollständig gebastelt hatte und jetzt lediglich das Gleiche in ASP/C# umsetzen muss. In meinem jugendlichen Leichtsinn, dachte ich im ersten Moment dass es nicht lange dauern kann. Hab ja schon die gesamte Logik und muss das ja nur schnell mal von PHP nach C# „übersetzen“. Diese Meinung war vor allem geprägt davon, dass ich die Businesslogik innerhalb des Web System mit XML und XLST erledigt hatte. D.h. ich muss nur die gleichen XML Daten generieren und mit den gleichen Stylesheets transformieren. Ganz klar ging ich davon aus, dass 90% der gesamten Logik in allgemeingültiger Form bereits vorhanden sind. Dazu kommt, dass ich die Kommunikation mit dem Backend mit JQuery und Javascript erstellt und das komplette Layout war in HTML mit CSS3 gesetzt. Hier und da hab ich noch ein JQuery Plugin verwendet. So what! Ich muss ja nur ein bisschen das XSLT und die AJAX Verbindungen auf der Serverseite umbiegen.

Also erstmal Kaffee gekocht und die ersten vier Stunden gingen dafür drauf Visual Studio runterzuladen, Updates und Servicepacks zu installieren. Danach dann lizenzieren, Umgebung konfigurieren und die ersten paar Code-Schritte zu testen. Hierbei ist mir bereits klargeworden, dass es wesentlich länger dauert, wenn ich etwas codiere und mir das Ergebnis im Browser anzuschauen. Bei jedem Klick auf den Play-Button im Visual Studio, wird immer erst Codestruktur geprüft, kompiliert und im Lokalen Webserver deployed. Das dauert etwa 3-4 Sekunden bis überhaupt das Ergebnis im Browser anfängt zu laden. Übrigens, bei Java Projekten in Eclipse dauert das auch so lange bis man irgendwas im Browser überhaupt sehen kann.

Die gleichen Aktivitäten im LAMP Stack, kostet überhaupt keine Zeit. Klar, der Code wird nicht überprüft oder kompiliert. Das Deployment in den Webserver fällt bei mir auch weg, da ich bei PHP immer im Root Verzeichnis meiner Webseite entwickele. Der Vergleich klingt jetzt etwas merkwürdige, da ich im Grunde technologisch Äpfel mit Birnen vergleiche. Lasst mal das Technische weg und seht es mal rein vom Blickwinkel der Zeit die für das Entwickeln drauf geht. Da kommen im Laufe von ein paar Sprints schnell ein paar Minuten zusammen wo man nur auf das Ergebnis wartet. Und aus Minuten werden irgendwann auch Stunden…

Zeit ist ja ein entscheidender Faktor bei der kommerziellen Softwareentwicklung, da alles letztendlich unter finanziellen Gesichtspunkten entschieden wird. Hierbei sind die Aufwandsschätzungen der Entwickler ein wichtiger Faktor. Nur wenn gut und realitätsnah geschätzt wird, haben Projekte überhaupt eine Chance im finanziell „Grünen-Bereich“ zu kommen. Zum Thema Aufwandsschätzungen habe ich bereits ein paar Artikel verfasst, schaut einfach mal nach Planning Poker in agilen Entwicklungsprojekten mit SCRUM oder Der agile Festpreis. Dort habe ich jeweils die Aspekte für Zeit und Geld bereits genauer betrachtet.

Neben der Zeit die für das Warten auf das Ergebnis drauf geht, bin ich auch erstaunt wie viel Platz auf den Festplatten für die Projekte drauf geht. Im oben genannten PHP Projekt, hab ich mit allen Frameworks, Bilddaten und Plugins keine 800 Kilobytes auf der Festplatte des Webserers im Beschlag. Das vergleichbare C# Projekt hat etwa 2 Megabytes konsumiert. Nur noch mal zur Erinnerung, in beiden Webseiten stecken die gleichen XSLT, Bilddaten und Javascript Framework drinnen! Das C# Ergebnis ist fast 2,5 mal größer als das vergleichbare PHP Projekt mit dem gleichen Leistungsumfang.

Der verbrauchte Plattenplatz ist heute längst nicht mehr so kostenintensiv wie früher, trotzdem ist es ein Faktor in der ökonomischen Betrachtung. In Summe kann ich keine Vergleichsmatrix aufbauen, hierbei gehen Einsatzgebiete von C#, PHP und Java zu weit auseinander. Obendrein hab ich weder die Zeit oder den Platzkonsum der einzelnen Entwicklungsumgebungen vergleichbar gemacht. Wichtig an diesem Artikel ist, dass die genutzte Technologie nicht nur Auswirkungen auf die Funktionsweise eines Produktes hat, sondern auch direkte Auswirkung auf die Kommerziellen Aspekte. Allein durch die Nutzung der unumgänglichen Entwicklungsumgebungen, ist der Aufwand für C# oder Java Entwicklung immer größer als in einer Entwicklungsumgebung „weniger-abhängigen“ Sprache wie PHP. Man kann PHP Entwicklungsumgebungen nutzen, muss es aber nicht wenn man es nicht will. Diese Option gibt es nicht für C# oder Java.

Ich werde für mich in Zukunft die vorgenannten Faktoren stärker in Betracht ziehen, wenn ich nach Aufwand für die Umsetzung befragt werde.

http://www.agile-coding.net/oekonomie-in-der-agilen-softwareentwicklung/

Cookies mit PHP verwenden

Unter Cookies versteht man einen begrenzten Speicherbereich innerhalb des Browsers, der vom Server gesetzt und ausgelesen werden kann. Mit Cookies kann man quasi benutzerbezogene Informationen ablegen und vom Client und Server aus benutzen. Cookies sind im Grunde standardisiert und eigentlich alle Browserorientierte Programmiersprachen können Cookies verwenden. PHP bietet die Methode setcookie() zum setzen und die Globale Variable $_COOKIE zum Auslesen an.

Wie man die Daten innerhalb des Cookies organisiert, kann man selbst bestimmen. Viele Entwickler gehen her und setzen die Daten innerhalb des Cookies mit einem Trennzeichen. Im Folgenden Beispiel, werden drei Informationen (Username, SessionID und Role) in einen Cookie gesetzt. Die Daten werden mit einem Semikolon getrennt abgelegt:

/*combine the values */
$value = $user.';'.$sessioID.';'.$role;
 
/* set the values in the cookie */
setcookie("name of the cookie", $value, time()+3600)

Jeder Cookie muss man mit einem Namen versorgen, man kann hier zum Beispiel den Namen der Domain nehmen. Zusätzlich sollte jeder Cookie eine Haltbarkeitsdatum erhalten. Damit man der Browser die gespeicherten Informationen auch wieder vergessen kann. Das mach Sinn, wenn man eine Benutzer-Session über Cookies steuert. Ihr habt das bestimmt schon bei Websiten gesehen, wo man sich anloggen muss. Viele bieten beim Einloggen eine Option an, eingeloggt zu bleiben. Eine solche Funktion wird im Regelfall über Cookies realisiert.

Um die Informationen aus dem oben gesetzten Cookie wieder auslesen möchte, muss man in PHP die Globale Variable $_COOKIE auswerten:

/*pull the data in local var*/
$s = $_COOKIE; 
 
/*explode the data into dedicated vars*/
@list($user, $sessionID, $role) = explode(";", $s);

Zu den bereits genannten Optionen für einen Cookie, kann man noch optional angeben, ob der Cookie über eine verschlüsselte Verbindung genutzt werden soll (secure) und zusätzlich kann man unterbinden, dass der Cookie von anderen Browsersprachen wie Javascript (httponly) genutzt weden kann.

Man sollte aber immer sehr genau nachdenken, welche Informationen man in einen Cookie setzt. Wenn man wichtige Informationen klarlesbar in einen Browser setzt, kann das im Grunde zu einer Sicherheitslücke führen. D.h. das obige Beispiel darf man so nicht in der Produktion verwenden. Da der Speicherplatz in einem Cookie begrenzt ist, sollte man sparsam damit umgehen.

Vergesst bitte auch nicht, dass jeder User im Browser die Verwendung von Cookies einschränken kann. D.h. es besteht das Risiko, dass einzelne User gar keine Cookies akzeptieren oder viel kurzfristiger als man es erwartet, vom Browser einfach gelöscht werden.

http://www.agile-coding.net/cookies-mit-php-verwenden/

Der agile Festpreis

Hinter den Worten „agiler Festpreis“ versteckt sich ein sehr interessanter Ansatz, um die Probleme mit den kommerziellen Aspekten von agilen Entwicklungsmethoden (z.B. Scrum) zu lösen. Das Problem steckt vorallem in den heute üblichen Verträge, die meistens einen fest definierten Funktionsumfang beschreiben und auf deren Grundlage die Schätzungen von Personentagen, dann ein Fixpreis berrechnet. Die Veränderung des Funktionsumfanges oder meiste daraus resultierenden Änderung der Personentage, führt immer zu einem unangnehmen Diskussionsbedarf. Der agile Festpreis bietet genau für dieses Problem mögliche Lösungswege an.

Grundliegendes Element für die Etablierung eines agilen Festpreis Vertrages zwischen den Vertragspartnern ist ein offenes und vertrauensvolles Verhältnis. Die Nutzung für ein solches Vertragswerk im Neukundenumfeld nur schwierig zu realisieren. Einfacher ist es mit Vertragspartnern die bereits miteinander Erfahrungen gesammelt haben. Der Kunde muss schließlich das Vertrauen zu den Prognosen des Anbieter haben, um bei den Verhandlungen des Vertragswerkes bereits sicher zu sein, dass das gewünschte Produkt am Ende der Entwicklung auch funktioniert.

Die Funktionsweise des agilen Festpreises beginnt in der Erarbeitung des Produkt Backlogs. In dieser Phase müssen Epics aufgenommen und nach und nach werden detailiert. Unter einem Epic verstehen man quasi einen bestimmten Themenblock, dem User stories zugewiesen werden. Als Beispiel für ein Epic könnten man eine User Verwaltung nennen und eine dazugehörige User story könnte die folgende sein: Als ein Benutzer möchte ich mich selbst registrieren können. Dieses Product Backlog erstellen die Vertragspartner am besten innerhalb von Workshops. Je nach Größe des Vorhabens können das auch mehrere Workshops sein, zumindest bis zu dem Punkt wo es keine neuen Ideen entstehen. In der Scrum Terminologie sind die Teilnehmer dieser Workshops meistens die sog. Stakeholder. D.h. der Personenkreis der bestimmt was das Product können soll. Wenn das Product Backlog nun existiert und soweit konkretisiert ist, dass das Scum Team mit den Schätzungen anfangen kann, dann muss das Product Backlog vor dem Hinzukommen neuer Epics geschützt werden. Jetzt muss das Scrum Team die einzelnen Stories schätzen und falls User Stories nicht fein genug detailiert wurde, ist der Product Owner gefragt, diese offenen Stories mit den Stakeholdern zu klären. Ob und wie eine User Story als „abarbeitbar“ definiert, nennt man Definition of Ready (DOR). Entspricht eine User Story nicht diesem Standard, wird das Team diese auch nicht in die Schätzung mit aufnehmen. Wenn das Scrum Team seine Arbeit beendet hat und alle Storypoints summiert sind, kann die nächste Phase der Vertragsverhandlungen beginnen. Die Vertragpartner kennen jetzt quasi den maximalen Funktionsumfang und die dafür nötigen Storypoints. Man kann quasi vergleichen, dass der eingangserwähnte fest definierte Funktionsumfang und die Schätzungen von Personentagen vorliegen. Nun muss der Anbieter nur noch den Geldwert für einen Storypoint nennen und schon kann man die Gesamtkosten der Produktentwicklung ausrechen. Wie kommt man jetzt aber an diesen Multiplikator? Hierfür muss die durschnittliche Verlocity eine Scrum Teams nehmen, durch die Anzahl der Teammitglieder teilen und mit dem typischen Internen Kostensatz für einen Mitarbeiter multiplizieren. Der Interne Kostensatz für Mitarbeiter benutzen viele Unternehmen bereits für die Berechung von Stundensätzen. Zusätzlich darf man nicht vergessen noch den den Gewinnen in dieser Rechnung mit auf zunehmen. Eine Beispiel Rechnung könnte so aussehen:

Durschnittliche Velocity des Teams ist 35
Das Team besteht aus 7 MitgliedernDer Interne Kostensatz liegt bei 70,- €
Der geforderte Gewinn liegt bei 100,-€

Daraus ergibt sich die Rechnung: 35 / 7 * (70 + 100) = 850,- €

Der Preis für einen Storypoint  wird vertraglich fixiert und wird im Verlauf des Entiwicklungsprozess auch nicht verändert. Die Zahl sollte trotzdem kurz vor der Vertragsverhandlung berechnet werden, da sich die durschnitlliche Velocity im Verlauf eines Jahres verändern kann.

Im Verlauf dieser Phase der Vertragsverhandlungen wurde nun der Gesamtumfang und die Gesamtkosten des Entwicklungsvorhabens ermittelt. Weiterhin wird nun die Summe der Storypoints festgelegt, die das Scrum Team mindestens im Verlaufe jedes Sprints erreichen muss. Diese Festlegung erlaubt nun das Berechnen des spätesten Fertigstellungsdatum. Zusätzich kann diese Festlegungen nun dazu dienen, dass die Vertragspartner das Vorhaben vorzeitig beenden können. Wenn quasi die Lieferquote aus irgendwelchen Gründen nicht eingehalten werden kann, ist es den Vertragspartnern möglich den Vertrag zu kündigen.

Da nun der späteste Liefertermin bekannt ist, wird auch der Zahlungstermin definiert. Im besten Fall einigen sich die Vertragparteien auf die Zahlung von Sprints. D.h. immer wenn Teilabnahmen im Rahmen des Scrum Reviews Meetings durchgeführt wurde, wird eine Teilrechnung fällig. Die Vorteile dieser Methode liegt klar auf der Hand, der Lieferant hat keine lange Vorfinanzierung zu leisten und der Kunde erhält zeitnahe ein funktionierendes Inkrement des gewünschten Produktes.

Das Thema des agilen Festpreises ist sicherlich wesentlich umfangreicher als ich es hier in diesem Artikel darstellen kann. Es gibt am Markt Bücher die mehrere hundert Seiten starkt sind. Zusätzlich hat jedes Unternehmen eine andere Implementierung aus dem Scrum Framework. Somit muss auch die Implementierung des agilen Festpreises für jedes Unternehmen in angepasster Form durchgeführt werden. Trotzdem kann ich es aber sehr empfehlen, zu dem Thema zu recherchieren und zu prüfen ob man Methoden auf dem Gesamtkonstrukt anwenden möchte. Ich halte es für eine sehr interresante Variante, um die kommerziellen Probleme in der agilen Softwareentwicklung anzugehen.

http://www.agile-coding.net/der-agile-festpreis/

Vorlage für User Stories (User Story Template)

User Stories sollten nach einem bestimmten Muster erstellen und im Product Backlog verwaltet werden. Wenn man User Stories immer nach dem gleichen Muster gestaltet, erhöht man die Chance im Team ein gemeinsames Verständnis für die Aufgabe zu erlangen.

Jede User Story sollte die folgenden informellen Aspekte erfüllen:

  • Wer wird diese Feature in erster Konsequnz nutzen?
  • Gibt es noch weitere Nutzer?
  • Was muss im Endeffekt getan werden?
  • Welchen „geschäftlichen“ nutzen hat das Feature?
  • Mit welchen anderen Features hängt es ggf. zusammen?

Je nachdem wie das Team User Stories definieren möchte, könnte man noch die folgenden Informationen ebenfalls hinzufügen:

  • Wie wird das Feature demonstriert?
  • Wie wird das Feature getestet?

Um alle diese Informationen in eine User Story aufnehmen zu können, sollte man jede User Story nach einer gewissen Strutkur aufbauen. Eine der meisten verwendeten Vorlagen ist gestaltet sich nach dem folgenden Muster:

Als ein Administrator von der Benutzerverwaltung, möchte ich über die Anzeigeliste der Benutzerkonten der Lage sein, Benutzerkonten vollständig zu administrieren. D.h. ich möchte Benutzerkonten anlegen, bearbeiten und löschen können.  Jedes Benutzerkonto muss die folgenden Informationen haben:

  1. Benutzername (Mindestens 8 Zeichen, der Benutzername darft nicht die eMail Adresse sein)
  2. Anrede (Herr oder Frau als Auswahlbox)
  3. Vollständiger Name (Vor- und Nachname getrennt)
  4. Adresse (Strasse, Hausnummer, Postleitzahl und Ort getrennt)
  5. eMail Adresse (Eingabe auf „@“ und „.“ zu prüfen)
  6. Passwort (Mindestens 6 Zeichen, bestehend aus min. einem Großbuchstaben und min. 2 Sonderzeichen)

Die Möglichkeit des Anlegens, Editierens und Löschens sollen in einzelnen Dialogen realisert werden. Ich benötige diese Feature, um Benutzerkonten losglöst vom Registrierungsprozess verwalten zu können.

Diese Beispiel User Story beantwortet alle Fragen aus der obigen Anforderungsliste. Überprüfen wir diese Festellung an Hand der gestellten Fragen:

  • Wer wird diese Feature in erster Konsequnz nutzen? Administratoren
  • Gibt es noch weitere Nutzer? Nein
  • Was muss im Endeffekt getan werden? Benutzerkonten Bearbeitunsfunktionen auf der Liste der Benutzerkonten durchzuführen
  • Welchen „geschäftlichen“ nutzen hat das Feature? Eigenständige Verwaltung der Benutzerkonten durch den Administrator
  • Mit welchen anderen Features hängt es ggf. zusammen? Mit dem Registrierungsprozess und der Übersichtsliste der Benutzerkonten

Man findet in User Stories explizite und impliziete Anforderungen. Ich fokusiere das Teams gerne auf die implizieten Anforderungen und spreche diese in den Scrum Meetings durch. Das ist wichtig da impliziete Anforderungen immer durch die Wahrnehmung jedes Team Mitgliedes ggf. anders Wahrgenommen werden können als explizite Features. Um das Risiko eines Missverständnisses zu verringern, muss man als Scrum Master darauf achten, dass die Diskussion innerhalb der Gruppe die Features von allen Seiten betrachtet. Ich versuche daher gerne ähnliche Umsetzungen in anderen Produkten als Beispiel zu zeigen. Zu dieser Anforderung an eine Benutzerverwaltung würde ich z.B. die Benutzerverwaltung von WordPress zeigen, um die Diskussion über die User Story zu unterstüzen.

Wichtig ist es in den Grooming Sessions und in der Sprint Planung muss das Team die User Story mit Informationen zur Umsetzung anreíchern. Das geschieht meistens automatisch innerhalb der Diskussion. Während der Diskussion enstehen meistens Fragen zu den einzelnen User Stories und diese Fragen müssen geklärt und an die betreffende Stelle in der User Story geschrieben werden. Im obigen Beispiel erkennt man diese zusätzlichen Informationen aus der Disskussion innerhalb der Klammern.

User Stories sind „Lebendegebilde“ und werden von Meeting zu Meeting verändert, erweitert oder neu aufgeteilt. Sollte das Team feststellen, dass eine User Story eigentlich zu groß und damit zu unverständlich ist, Muss man versuchen Teile der User Story in eigene User Stories herunter zu brechen und neu zu definieren. Im obigen Beispiel könnte man zum Beispiel das Anlegen, oder das Editieren in eigene User Story auslagern.

Das gemeinsame Verständnis innerhalb des Teams für den Inhalt einer User Story sehr wichtig. Für manche Teams kann es hilfreich sein eine Definition of Ready DOR zu definieren. Die DOR defniert welche Kriterien eine User Story erfüllen muss, um als READY zur Umsetzung zu betrachten. Das hilft dem Product Owner bereits frühzeitig zu erkennen, welche Informationen für eine User Story bereitgestellt werden müssen und erleichtert in der Sprint Planung die Auswahl von einzelnen User Stories für den angehenden Sprint.

http://www.agile-coding.net/vorlage-fuer-user-stories-user-story-template/

PHP Webdriver Tests mit Jenkins

Ich hatte in der Vergangenheit bereits etwas über den Einsatz von Selenium zum Automatisierten Testen von Webanwendungen im Artikel Automatisierte Browsertests mit Selenium und PHP Webdriver geschrieben. Man sollte auch diesen Artikel zunächst gelesen haben, um die weiteren Informationen in diesem Artikel besser verstehen zu können.

Heute möchte ich eines meiner automatisierten PHP Testskripten erläutern, welches die Funktion einer Website von verschiedenen Plattformen (Window7, WindowsXP und Suse Linux Enterprise Server) automatisch testet. Im Grunde wird natürlich die Funktionsweise einer Website in verschiedenen Browser (Internet Explorer, Firefox und Chrome) auf den genannten Plattformen überprüft.

Damit man das folgende PHP Script einsetzen kann, muss man verstehen dass es für eine bestimmte Systemumgebung konzipiert ist. Das PHP Skript ist ein Test-Plan mit einzelnen Testfällen. Jeder einzelne von den Testfällen wird auf den drei vorgenannten Testservern ausgeführt und testet die einzelnen Testfällen in den installierten Browsern. Bevor man beginnen kann, muss man zunächst mindestens einen Test Plattform mit Selenium aufbauen, hierfür findet man die Anleitung in dem Artikel Webdriver Testsystem für Selenium installieren und einrichten. Ohne das hat das Weiterlesen an dieser Stelle keinen Sinn 😉

Im ersten Schritt sollte man das PHP Script auf die eigene Testumgebung konfigurieren. Hierfür stellt man im Bereich settings and preparations erstmal die verfügbaren Browsern auf den Testsystemen ein. Hierfür existiert für jedes Testsystem ein Array in dem die Namen der zu testenden Browser definiert werden. Die Arrays Win7, WinXP und SLES werden daher einfach mit den verfügbaren Browsern bestückt. D.h. wenn man auf dem Testsystem für Windows 7 (Win7) nur einen Internet Explorer testen möchte, muss man dem entsprechend alle anderen Browser aus dem Array entfernen.

Wenn man nun die Browser auf den Testssystemen definiert hat, muss man natürlich noch die URL zu dem Testserver bekannt geben. Dieses URL zielt auf den Installierten Selenium Webdriver und wird in der Varibale $host gespeichert. Z.B. für den Window 7 Testserver habe ich definiert: $host = ‚http://win7-selenium-server:4444/wd/hub‘

Denkt daran, man muss vorher den Selenium Webdriver auf dem Testsystem installiert und gestartet haben.

Damit man mit dem automatisierten Testen endlich loslegen kann, muss man erstmal einen Testcase definieren und programmieren. Hierfür nutze ich gerne den Selenium Builder, um den Testcase aufzunehmen und dann den PHP Code direkt zu exportieren. Ihr findet eine kurze Zusammenfassung im Artikel Firefox Plugin Selenium Builder. Der Selenium Builder ist ein wirklich komfortables Tool um direkt den Testfall so zu strukturieren, wie er im Bereich Testcase in diesem PHP Script benutzt wird.

In dem folgenden PHP Script wird ein einfacher Testfäll ausgeführt. Der Testfall ruft ein Kontaktformular auf und füllt alle Felder mit sinnvollen Werten aus. Danach wird das Formular abgeschickt und die Erfolgsmeldung abgefragt. Wenn das funktioniert, gilt der Testfall als erfolgreich und das PHP Script endet mit dem Returncode 0. Wenn ein Fehler bei diesem Test auftritt wird der Returncode 1 gesetzt und ein Screenshot des Browsers gemacht. Somit hat man neben dem Fehlertext auch ein Visuelles Mittel um den Fehler bessern nachvollziehen zu können.

<?php
/*
@copyright          2014
@author             Andreas Baßermann
@email              ab@agile-coding.net
@created            24.04.2014
@file-summary       This file is a Template for automatic browser testing with selenium php-webdriver
                    You got to loop a testcase within the try´n catch
                    When you going to check a test and recognize errors, throw an exception, the script is doing 
                    a screenshot and sets the returncode
*/
 
/*
##########################################################################
 
    testplan [Namen], [Version]
 
##########################################################################
*/
 
/*
##########################################################################
 
    settings and preparations
 
##########################################################################
*/
 
/* including the Webdriver framework */
require "PHPWebDriver/__init__.php";
 
/* setting invinite timelimit for the script, test can run a long time  */
set_time_limit(0);
 
/* setting some vars of the script */
$tpname = 'Browser compatibily Firefox'; //name of the testplan
$tpversion = '1.0'; //version of the testplan
$selenium ='http://test-host/site'; //path of the server where the testscript runs 
 
/* define which OS and Browser should be tested, it obvious, Internet Explorer is not possible on Linux (SLES) */
$Win7 = array(    "internet explorer",
                "chrome",
                "safari",
                "firefox");
 
$WinXP = array(    "internet explorer",
                "chrome",
                "firefox");
 
$SLES = array(    "firefox");
 
/*
##########################################################################
 
    preparing the test on diffrent environment
 
##########################################################################
*/
/* looping the browser on windows7 testsystem */
foreach ($Win7 as $system){
 
    /* setting the selenium server */
    $host = 'http://win7-selenium-server:4444/wd/hub';    
    
    switch ($system) {
        case "internet explorer":
            $browser = "internet explorer";
            break;
        case "chrome":
            $browser = "chrome";
            break;
        case "safari":
            $browser = "safari";
            break;
        case "firefox":
            $browser = "firefox";
            break;
    }
 
    /* fire the testcases */
    do_tests($selenium, $host, $browser, $tpname, $tpversion);    
}                    
 
/* looping the browser on WindowsXP testsystem */
foreach ($WinXP as $system){
 
    /* setting the selenium server */
    $host = 'http://win-xp-selenium-server:4444/wd/hub';    
 
    switch ($system) {
        case "internet explorer":
            $browser = "internet explorer";
            break;
        case "chrome":
            $browser = "chrome";
            break;
        case "safari":
            $browser = "safari";
            break;
        case "firefox":
            $browser = "firefox";
            break;
    }
    
    /* fire the testcases */
    do_tests($selenium, $host, $browser, $tpname, $tpversion);    
}    
 
/* looping the browser on Linux (SLES) testsystem */
foreach ($SLES as $system){
 
    /* setting the selenium server */
    $host = 'http://linux-selenium-server:4444/wd/hub';
 
    switch ($system) {
        case "internet explorer":
            $browser = "internet explorer";
            break;
        case "chrome":
            $browser = "chrome";
            break;
        case "safari":
            $browser = "safari";
            break;
        case "firefox":
            $browser = "firefox";
            break;
    }
    /* fire the testcases */
    do_tests($selenium, $host, $browser, $tpname, $tpversion);    
}    
    
/*
##########################################################################
 
    setting success returncode
 
##########################################################################
*/
exit(0);
 
/*
##########################################################################
 
    The Tests 
 
##########################################################################
*/
function do_tests($selenium, $host, $browser, $tpname, $tpversion){
/*
    Starting the single tests
*/
    try {
    
        /* creating the session to the selenium server */            
        $driver = new PHPWebDriver_WebDriver($host);        
 
        /* defining the session for the webdriver */
        $session = $driver->session($browser);
 
        /* always maximizing the remotebrowser*/
        $session->window()->maximize();
    
        /*
        ##########################################################################
 
            testcase # 1 
            
            [copy the entire testcase for creating another one]
 
        ##########################################################################
        */
 
        /* while creating you can skipt singel testcases, do not forget setting fals for production */
        $skiptit = TRUE;
        
        if ($skiptit == FALSE){
            /* setting the vars for the testcase */
            $tcname = 'Browser compatibily '; //name of the testcase
            $tcnumber = '1'; //number of the testcase
            
            /* open the page */
            $session->open('http://172.16.70.12/er-bau/kontakt/kontaktformular/');
 
            //checking the page titel, if not Kontaktformular, then stop the entire test
            if (! (strpos($session->element("tag name", "html")->text(), "Kontaktformular") !== false)) {
                throw new Exception('headline Kontaktformular wasn found');
            };    
 
            /* fill the contact form with some datas */        
            $result = $session->element('id', 'firstname');
            $result->sendKeys('Peter');
            
            $result = $session->element('id', 'surename');
            $result->sendKeys('Lustig');
            
            $result = $session->element('id', 'tel');
            $result->sendKeys('12345678');
            
            $result = $session->element('id', 'email');
            $result->sendKeys('test@netzwerk-design.de');
            
            $result = $session->element('id', 'address');
            $result->sendKeys('Ludwigstrasse');
            
            $result = $session->element('id', 'str-number');
            $result->sendKeys('8');
            
            $result = $session->element('id', 'plz');
            $result->sendKeys('63217');
            
            $result = $session->element('id', 'city');
            $result->sendKeys('Offenbach');
            
            $result = $session->element('id', 'subject');
            $result->sendKeys('Test Mail Kontaktformular Windows 7 - Firefox');
            
            $result = $session->element('id', 'message');
            $result->sendKeys('Dies ist ein Test des Kontaktformular.');
            
            // submit the form
            $result = $session->element('id', 'submit')->submit('');
            
            //need a break to wait for reload of the page
            sleep(10);        
            
            // check if the respondse message is correct
            if ($session->element("class name", 'wpcf7-response-output')->displayed('') == 0) {
                throw new Exception('succsess respond message "wpcf7-response-output" wasn´t shown');
            };
        }
        /*
        ##########################################################################
 
            testcase # 2 
 
        ##########################################################################
        */
        
    }
    catch (Exception $e){
        
        /* dumping the error message */
        echo $e.PHP_EOL;
        
        /* dumping the environment  */
        var_dump($session->capabilities());
        
        /* finally take a screenshot and save it with filename reference to this testcase */
        echo 'screenhot: '.get_screenshot($session, $tpname, $tpversion, $tcname, $tcnumber, $selenium);
        
        /* cleaning up and closing session */
        $session->close();
        exit(1);
    }
    /* closing the session to the selenium server */
    $session->close();
}
/*
##########################################################################
 
    functions 
 
##########################################################################
*/
function get_screenshot($session, $tpname, $tpversion, $tcname, $tcnumber, $selenium){
    /* setting the vars for a screenshot */
    $img = $session->screenshot();
    $data = base64_decode($img);
    $file = $tpname.'_'.$tpversion.'_'.$tcname.'_'.$tcnumber.'.png';
 
    /* finally take a screenshot and save it with filename referenz to this testcase */
    try{
        $sc = file_put_contents($file, $data);
        $scfile = $selenium."/".$file.PHP_EOL;  
    }
    catch (Exception $e){
        echo 'it was not possible to generate a screenshot'.PHP_EOL;
        echo $e.PHP_EOL;
        exit(1);
    }
    return $scfile;
}
?>

Ich benutze das Skript im Zusammenhang mit Jenkins. Genauer gesagt, der Jenkins wird benutzt um die entwickelte Website auf einem Webserver zu installieren und im Nachgang wird dieses PHP Script ausgeführt, um die definierten Testfälle ab zu prüfen. Wenn die Auslieferung und die Testfälle erfolgreich waren, endet der Jenkins Job erfolgreich und ich kann mir sicher sein, dass die Qualität der Website weiterhin gegeben ist.

http://www.agile-coding.net/php-webdriver-tests-mit-jenkins/