16.12. Eine Einführung in Behavior Driven Development

Testdriven Development, also das Schreiben eines oder mehrerer Tests bevor der eigentliche Code entsteht, ist inzwischen ein alter Hut.
Ein großer Nachteil dieses Verfahrens ist, dass im agilen Umfeld die User Stories erst verstanden werden müssen.
Wenn die Story aber falsch verstanden wurde, dann wird auch der Test falsch implementiert.
Knackpunkt ist also immer noch der Abgrund zwischen Analyse und Verdeutlichung der Geschäftsprozesse sowie dem korrekten Erfassen und testen eben jener.
Eine Lösung hierfür soll Behavior Driven Development – kurz BDD – bieten.

Was ist BDD?

BDD gibt eine strukturierte Sprache vor, in welcher Businessprozesse beschrieben werden.
Diese Beschreibung eines Features wird durch ein oder mehrere Beispiele, die das Verhalten korrekt darstellen, verdeutlicht und über diese getestet.
Konkret heißt dies, dass im Prinzip auch Steakholder aus der Managementebene entsprechende Anforderungen definieren können, die dann in sog. Szenarios resultieren, also konkreten Testfällen.
So lässt sich vermeiden, dass ein Steakeholder mit dem Produkt unzufrieden ist, da das Produkt genau dem entspricht was er definiert hat.
Was hier wie Magie klingt ist in der Praxis recht simpel, wie wir im folgenden sehen werden.

Behat

In der Ruby-Welt hat sich hierfür Cucumber etabliert.
Da wir aber kein Ruby sondern PHP nutzen, und uns damit auch besser auskennen, greifen wird auf Behat zurück.
Behat ist eine Portierung von Cucumber auf PHP Basis, welche intern auf Komponenten des Symfony Projekts zurückgreift und vollständig inkompatibel ist zu PHP 5.3.
Installiert wird dies am einfachsten per PEAR durch Eingabe der folgenden Befehle:

pear channel-discover pear.symfony.com
pear channel-discover pear.behat.org
pear install behat/behat

Zudem ist eine Installation per GIT verfügbar, sowie ein PHAR Package, welches sich direkt ausführen lässt.

Das erste Projekt

Da aber alle Theorie öde und grau ist, starten wir gleich mit einem ersten Projekt.
Zu Beginn soll hier eine einfache Kommandozeilenanwendung stehen, die einen ebenso einfachen Taschenrechner implementiert.
Dazu legen wir uns erst einmal ein Projekt an, indem auf der Kommandozeile in den Projektordner gewechselt und ein neues Behat Projekt initialisiert wird.
Dies geschieht mit: behat –init

Hier wird uns nun ein Ordner mit dem Namen features erstellt.
In diesem landet die Beschreibung dessen was unsere Anwendung leisten soll.
Die Anforderungen werden in BDD in sog. features unterteilt.
Ein Feature beschreibt dabei eine Funktionalität, ähnlich einer User Story, und enthält die Testfälle um sicherzustellen, dass das Feature die gewünschte Funktionalität abdeckt.
Für jedes Feature wird eine eigene Datei erstellt, die den Namen des Features trägt und auf .feature endet.
Unser erstes Feature soll die Addition sein, also legen wir eine Datei mit dem Namen add.feature im Verzeichnis features an.
Hier beschreiben wir zunächst was unser Feature tun soll, indem wir mithilfe des Tags Feature angeben, dass wir ein Feature beschreiben wollen und nach einem Doppelpunkt dieses beschreiben:

Feature: Addition
	Add two given numbers and return the sum.

Soweit unterscheidet sich das Vorgehen nicht drastisch vom Erstellen einer User Story.
Kommen wir nun zu den Akzeptanzkriterien, welche in BDD „Szenarios“ heißen und Testfälle beschreiben, welche bestanden werden müssen.
So wäre zum Beispiel eine Anforderung für unsere Addition das die Zahlen zwei und drei in Summe fünf ergeben.

Markiert wird ein Szenario durch ein Scenario, welches nach einem Doppelpunkt eine Beschreibung des Tests erwartet, sowie der eigentlichen Definition dessen was getestet werden soll.
Diese Definition besteht dabei immer aus einem Kontext, markiert durch das Stichwort Given, einem Event, When und einem Ergebnis, Then.

Unser Szenario sieht also wie folgt aus:

Scenario: Add two numbers
	Given I have entered 2 in the calculator
	And I have entered 3 into the calculator
        	When I press add
        	Then the result should be 5

In unserem Fall geben wir einen zweiten Kontext, unsere Zahl die aufaddiert werden soll, mit dem Stichwort And an.
Somit lassen sich auch weitere Events abbilden, die zu dem Ergebnis führen.

Das ist eigentlich alles was wir brauchen um loslegen zu können mit der Implementation.

Wein zu Wasser

Die Beschreibung was getestet werden soll ist an sich allerdings recht witzlos.
Was wir wollen sind Unittests, die ausgeführt werden können und mir sagen, ob mein Feature tut was es soll, oder ob ich da nochmal ran muss.
Code für unsere Tests können wir zum Glück anhand unseres Szeanrios generieren.
Dafür reicht im Projektverzeichnis folgender Befehl:

behat
<br />
Hier erhalten wir als Ausgabe, dass einige Schritte noch nicht abgebildet sind. <br />
In unserem Fall sind das Alle. <br />
<br />
<h2>Eigene Schritte gehen</h2>
<br />
Nun müssen wir also Behat sagen was wir mit den einzelnen Schritten im Testszenario meinen. <br />
Das geschieht in dem wir angezeigten Code erst einmal nur in die Datei <i>FeatureContext.php</i> kopieren, welche im Verzeichnis <i>bootstrap</i> zu finden ist.
An dieser Stelle soll kurz, ohne weitere Erläuterungen, der Aufbau unserer Klasse <b>Calculator</b> dargestellt sein:

<pre name=“code“ class=“php“>

class calculator
{
    protected $_first = 0;
    
    protected $_second = 0;
    
    protected $_sum;
    
    public function setFirst($first) 
    {
        $this->_first = (int) $first;
    }
    
    public function setSecond($second) 
    {
        $this->_second = (int) $second;
    }
    
    public function getSum()
    {
        return $this->_sum;
    }
    
    public function add()
    {
        //add first and second and put result into sum
    }
}

Wir haben nun also eine eigene Klasse, von der wir im Konstruktor des Feature Kontexts ein Objekt erstellen.

require_once realpath(„../calculator.php“);


class FeatureContext extends BehatContext
{
    protected $_calculator = null;	
    public function __construct(array $parameters)
    {
        if(!$this->_calculator) {
	$this->_calculator = new calculator();
        }
    }
[...]

Wenn wir jetzt Behat erneut ausführen bekommen wir zwar immer noch eine Fehlermeldung, aber die hilft uns schon weiter, da sie uns auffordert die fehlenden Schritte zu implementieren.
Also sagen wir Behat was in den einzelnen Schritten passiert, in den beiden Eingabeschritten setzten wir die Werte mit denen gerechnet wird, das Event Add soll eine Addition im Taschenrechner auslösen und innerhalb des Resultats überprüfen wir ob der übergebene und der addierte Wert gleich sind.
Hierfür verwenden wir die Funktion assertEquals.
Unser Feature Kontext sieht also in etwa wie folgt aus:

require_once realpath('../calculator.php');

/**
 * Features context.
 */
class FeatureContext extends BehatContext
{
    protected $_calculator = null;

    /**
     * Initializes context.
     * Every scenario gets it's own context object.
     *
     * @param   array   $parameters     context parameters (set them up through behat.yml)
     */
    public function __construct(array $parameters)
    {
        // Initialize your context here
        if(!$this->_calculator) {
            $this->_calculator = new calculator();
        }
    }

    /**
     * @Given /^I have entered (\d+) in the calculator$/
     */
    public function iHaveEnteredInTheCalculator($argument1)
    {
        $this->_calculator->setFirst($argument1);
    }

    /**
     * @Given /^I have entered (\d+) into the calculator$/
     */
    public function iHaveEnteredIntoTheCalculator($argument1)
    {
        $this->_calculator->setSecond($argument1);
    }

    /**
     * @When /^I press add$/
     */
    public function iPressAdd()
    {
        $this->_calculator->add();
    }

    /**
     * @Then /^the result should be (\d+)$/
     */
    public function theResultShouldBe($argument1)
    {
        $result = $this->_calculator->getSum();
        assertEquals($argument1, $result);
    }
}

Jetzt müssen wir nur noch schnell unsere add() Methode im Taschenrechner implementieren und dann sollte auch unser Testszenario sauber durchlaufen.
Sollte hierbei eine Fehlermeldung auftauchen, dass die Funktion asseertEquals nicht gefunden werden kann, so kann dies mit einem

require_once 'PHPUnit/Autoload.php';
require_once 'PHPUnit/Framework/Assert/Functions.php';

innerhalb des Feature Kontextes behoben werden.

Ist das alles?

Im Prinzip ist das alles was man mit Behat anstellen kann.
Wirklich spannend wird das Ganze aber erst bei entsprechend vielen Klassen und Szenarien.
So kann man zum Glück für ein Feature, innerhalb der selben Datei, mehrere Szenarien beschreiben.
Einfacher geht dies jedoch mit Szenario Outlines.
Diese Erweiterung macht es möglich, dass ein Szenario mit mehreren Werten ausgeführt wird.
Für unser Beispiel prüfen wir also zudem ob drei und vier auch wirklich sieben ergibt.
Dafür müssen wir Platzhalter innerhalb des Szenarios definieren und tabellarisch angeben mit welchen Werten diese Platzhalter in einem Testlauf ersetzt werden sollen.
Platzhalter werden hierbei wie einfache XML-Tags notiert.
Um dieses dynamische Szenario vom einfachen Szenario abzugrenzen wird statt Scenario das Stichwort Scenario Outline verwendet.
Unser dynamisches Szenario sieht also wie folgt aus:

Scenario: Add two numbers
	Given I have entered  in the calculator
	And I have entered  into the calculator
        	When I press add
        	Then the result should be 

| first | second | result |
|   2   |      3      |     5   |
|   3   |      4      |     7   |

Ausgeführt wird auch dieses Szenario mit dem schon bekannten behat.
Somit lassen sich auch aufwendigere Testszenarien mit minimalem Aufwand abbilden.

Fazit

Der Einstieg in Behavior Driven Development ist mit Behat mehr als Simpel, so dass ich nur jedem empfehlen kann, diese Vorgehensweise selber einmal auszuprobieren.
Wenn beim Schreiben der Szenarios auf Ausführlichkeit geachtet wird, so bekommt man hierdurch eine hohe Testabdeckung, die im Gegensatz zu manuell geschriebenen Test, genau die Anforderungen abdeckt, die für das jeweilige Feature bestehen.
Das wohl überzeugendste Argument welches für BDD steht ist jedoch die einfache Lesbarkeit der Features und Szenarien, mit welcher auch technisch nicht versierte Nutzer die Gelegenheit bekommen sich in das Projekt einzubringen.

2 Kommentare

  1. Last week I had the chance to attend an Agile Developer Skills Workshop in Berlin. The 3 day workshop is, next to a Scrum Master or PO Certification, a prerequisite for the Certified Scrum Developer, short CSD. I was very excited about the ADS wor

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert