Unit Tests mit PHPUnit und Dataprovider
Unit Tests sind zur Qualitätssicherung von Programmcode eine gute Möglichkeit, um auch nach mehreren Monaten sicher Änderungen am eigenen oder fremden Code vornehmen zu können. Dies setzt natürlich sorgfältig geschriebene Tests voraus. Mithilfe von Dataprovidern lässt sich der zu testende Code schnell und effizient mit einer Vielzahl an Testdaten testen.

Um einen Testfall mit PHPUnit definieren zu können, ist natürlich erstmal ein zu testender Code notwendig. Für diesen Artikel wird folgende PHP Klasse definiert, für die im weiteren ein Testfall definiert wird:

<?php

class EmailValidator
{
    /**
     * Check if provided email address is valid.
     *
     * @param string $email
     *
     * @return bool
     */
    public function isValid(string $email): bool
    {
        if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return true;
        }

        return false;
    }
}

Der Code der Klasse ist recht trivial und überschaubar: Über eine öffentliche Methode isValid kann geprüft werden, ob eine E-Mail Adresse valide ist,

Test mit PHPUnit

<?php

class EmailValidatorTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var EmailValidator
     */
    protected $emailValidator;

    /**
     * Setup the test class.
     */
    public function setUp()
    {
        $this->emailValidator = new EmailValidator();
    }

    /**
     * Test if the email validation validates email addresses.
     *
     * @dataProvider emailDataProvider
     *
     * @param string  $email
     * @param bool    $result
     */
    public function testEmailValidation(string $email, bool $result)
    {
        $validated = $this->emailValidator->isValid($email);

        $this->assertEquals($result, $validated, 'The provided email address is not valid.');
    }
}

In diesem Beispiel erwartet die Test Methode testEmailValidation zwei Parameter, die bei der Ausführung aus dem Dataprovider an die Test Methode übergeben werden. Durch die @dataProvider Annotation wird der Test Methode mitgeteilt, mit welchem Dataprovider die Methode verknüpft ist.

Dataprovider

Der Dataprovider selbst ist zunächst eine einfache Methode im Testfall, die als Rückgabe ein assoziatives Array zurück liefert:

<?php

/**
* Return the email validation dataProvider based on yaml fixture file.
*
* @return array
*/
public function emailDataProvider()
{
    return [
        ['test@example.com', true],
        ['invalidemail.com', false],
    ];
}

In der ersten Ebene sind die verschiedenen Testfälle definiert und in der zweiten Ebene werden die jeweiligen Parameter angegeben, welche in der gleichen Reihenfolge an die Test Methode übergeben werden. Dadurch verringert sich die Anzahl an Testmethoden im Testfall deutlich, ohne darauf zu verzichten, den zu testenden Code mit einer Vielzahl verschiedener Szenarien zu testen.

Das Array kann natürlich nach belieben auf beiden Ebenen an den jeweiligen Testfall angepasst werden.

Dataprovider via YAML

Sind in dem Dataprovider nur ein paar Testfälle oder nur ein paar wenige Parameter enthalten, so ist die Einbindung direkt im PHP Code des Testfalles kein größeres Problem, weil der Code trotz Dataprovider überschaubar bleibt. Anders jedoch sieht es aus, wenn viele verschiedene Testfalle definiert werden sollen oder wenn sich die Anzahl der Parameter für die Test Methode erhöht. Dann kann es schnell unübersichtlich werden und Zeilenanzahl im Testfall wächst. Möchte man zudem einen Dataprovider in verschiedenen Testfällen verwenden, wird schnell doppelter Code erstellt, was natürlich zu vermeiden ist.

Um also den Dataprovider auszulagern, bedient man sich der PHPUnit Erweiterung YamlDataSet. Da diese Erweiterung nicht Bestandteil des PHPUnit Composer Pakets ist, muss sie zunächst zusätzlich via Composer installiert werden:

$ composer require --dev phpunit/dbunit

Anschließend wird im Testfall die Methode, die den Dataprovider zurück liefert, wie folgt angepasst:

<?php

/**
* Return the email validation dataProvider based on yaml fixture file.
*
* @return array
*/
public function emailDataProvider()
{
    $data = new PHPUnit_Extensions_Database_DataSet_YamlDataSet(
        dirname(__FILE__) . '/../fixtures/Emails.yml'
    );

    $emails = [];
    $table  = $data->getTable('Emails');

    for ($i = 0; $i < $table->getRowCount(); $i++) {
        $emails[] = $table->getRow($i);
    }

    return $emails;
}

Anstatt den Dataprovider also direkt im Testfall zu definieren, wird eine externe YAML Datei eingelesen, in dem die einzelnen Testfälle und Parameter definiert sind.

Die YAML Datei für den Testfall sieht dabei wie folgt aus:

Emails:
    -
        email: test@example.com
        result: true
    -
        email: firstname.surname@example.com
        result: true
    -
        email: invalidemail.com
        result: false
    -
        email: randomstring
        result: false

Fazit

Mit Hilfe der Dataprovider lässt sich der eigene Programmcode schnell und effizient mit einer vielzahl verschiedener Testdaten testen und minimiert im gleichen Zuge doppelten Code, der ohne die Verwendung von Dataprovidern schnell entsteht. Durch die Minimierung von unnötigen Code wird zudem die Übersichtlichkeit in den Unit Tests verbessert, was insbesondere bei der weiteren Arbeit am Programmcode einen enormer Vorteil bietet.