Abschnittsübersicht



  • Wie werden die einzelnen Schritte eines Tests ausgeführt?

    Die einzelnen Testschritte sind jeweils einer PHP-Funktion zugeordnet. Diese Funktionen können schlicht Änderungen an der Moodle-Instanz vornehmen (z. B. Admin-Settings setzen) oder aber auf Verhalten prüfen und bei Fehlschlag eine "Exception" werfen.

    Die Zuordnung "Behat-Testschritt → PHP-Funktion" erfolgt über eine Kommentar-Annotation der entsprechenden PHP-Funktion. Werden die Behat-Tests mit der Flag --format=pretty ausgeführt, wird zu jedem durchgeführten Schritt die zugehörige PHP-Funktion angegeben.

    Beispiel:

    And I log in as "admin"            # behat_auth::i_log_in_as()
    
    

    Im Scope "behat_auth" befindet sich also eine Funktion "i_log_in_as()". Wird in einem Moodle-Verzeichnis nach "behat_auth" gesucht, so findet sich die Datei "auth\tests\behat\behat_auth.php". In dieser Datei befindet sich die Klasse behat_auth, die von behat_base erbt, und innerhalb der Klasse die entsprechende Funktion i_log_in_as().

    auth/tests/behat/behat_auth.php

    <?php
    [...]
    require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
    [...]
    class behat_auth extends behat_base {
     
        /**
         * Logs in the user. There should exist a user with the same value as username and password.
         *
         * @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)"$/
         * @Given I am logged in as :username
         * @param string $username the user to log in as.
         * @param moodle_url|null $wantsurl optional, URL to go to after logging in.
         */
        public function i_log_in_as(string $username, moodle_url $wantsurl = null) {
            // In the mobile app the required tasks are different (does not support $wantsurl).
            if ($this->is_in_app()) {
                $this->execute('behat_app::login', [$username]);
                return;
            }
     
            $loginurl = new moodle_url('/auth/tests/behat/login.php', [
                'username' => $username,
            ]);
            if ($wantsurl !== null) {
                $loginurl->param('wantsurl', $wantsurl->out_as_local_url());
            }
     
            // Visit login page.
            $this->execute('behat_general::i_visit', [$loginurl]);
        }
        [...]
    }
    
    Der PHPDoc-Kommentar oberhalb der Funktion enthält eine kurze Beschreibung und anschließend zwei Zeilen, die jeweils mit @Given beginnen und dem dann ein regulärer Ausdruck folgt. Diese beiden Zeilen verwendet Behat, um beim Schreiben eines Test-Szenarios die einzelnen Schritte der zugehörigen PHP-Funktion zuzuweisen und die Argumente entsprechend zu übergeben.

    So weit beurteilbar, muss es sich bei dem Wort unmittelbar nach dem @ um eines der drei Wörter eines Behat-Tests handeln: Given, When, Then. Unabhängig davon, welches Wort hier gewählt wird, kann dieser Testschritt auch mit den anderen Wörtern, insbesondere auch mit And und But, aufgerufen werden.

    Der darauffolgende Ausdruck gibt dann den Text wieder, der in dem entsprechenden Test-Szenario geschrieben werden muss, um diese Funktion aufzurufen. Der Ausdruck kann dabei sogenannte "named capture groups" beinhalten, die es einem ermöglichen, Text aus dem regulären Ausdruck zu extrahieren und in die in spitzen Klammern stehende Variable zu schreiben. So können die im Behat-Test beschriebenen Werte als Parameter der PHP-Funktion übergeben werden.

    Innerhalb der Funktion kann anschließend normales PHP verwendet werden. Handelt es sich bei dem Testschritt um einen "@Then"-Schritt, sollte die Funktion auf entsprechendes Verhalten prüfen und bei Fehlschlag eine entsprechende Exception werfen bspw. ExpectationExceptionElementNotFoundExceptionDriverException. Die Exceptions befinden sich jeweils in Behat\Mink\Exception\. Die DriverException entsteht in der Regel dadurch, dass versucht wird, eine JavaScript-Funktion auszuführen, obwohl der Test nicht entsprechend annotiert wurde (und deswegen die JavaScript-Engine des Browsers nicht zur Verfügung steht).

    JavaScript kann in der PHP-Funktion wie folgt ausgeführt werden:

    if (!$this->running_javascript()) {
        throw new DriverException("JavaScript-Engine is not running.");
    }
    $jsvariable = "<javascript-code-snippet>";
     
    $result = $this->evaluate_script($jsvariable);
    
    
    Sollte der JavaScript-Code einen Rückgabewert haben, wird dieser in $result gespeichert und kann anschließend ausgewertet werden .

    Neben der semantischen und syntaktischen Richtigkeit der Funktion gibt es auch noch Good-Practice-Regeln im PHP-Kontext zu beachten:

    • Die Funktion sollte exakt einen Schritt eines Behat-Test-Szenarios abdecken. Falls für das Szenario mehrere Schritte notwendig sind und die jeweiligen Funktionen nicht schon existieren, sollten entsprechend mehrere Funktionen geschrieben werden. Kleine atomare Schritte machen es beim Fehlschlagen des Tests einfacher nachzuvollziehen, was genau schiefgelaufen ist.
    • Die Stolpersteine eines Test-Schrittes sollten vorab durchdacht werden und die Funktionentsprechende Fehlermeldungen werfen. Beispielsweise:
      • Wird Javascript zur Durchführung benötigt? → Prüfe, ob die Javascript-Engine ansprechbar ist, sonst DriverException.
      • Soll ein bestimmtes Element auf der Website stehen? Prüfe, ob es auch tatsächlich existiert, sonst ElementNotFoundException.
      • Soll ein Element einen bestimmten Wert enthalten? Falls dieser Wert nicht vorliegt ExpectationException werfen.
    • Exceptions sollten aussagekräftige Mitteilungen mitgegeben werden, damit sich ein Bug schneller finden lässt. Bei einer ExpectationException empfiehlt es sich bspw. erwartete und tatsächliche Werte auszugeben.