Integration Tests mit Gürkchen – Teil 3

In dieser Artikelserie geht es um die lesbare Beschreibung von Integration-Tests mit Gherkin. Im ersten und zweiten Teil haben wir zwei Test-Szenarios geschrieben. In diesen Szenarios waren Fixtures (Testdaten) enthalten, gegen die anschließend getestet wurde. In diesem Teil statten wir das Ganze noch mit Dependency Injection aus, damit wir das eklige Singleton wieder entfernen können.


Leider ist die Dokumentation von Behat sehr Rookielastig – Basics werden gut erklärt, aber über tiefergehende Features gibt es wenig bis keine Anleitung oder Blogposts.

Somit bin ich erst dem Tipp eines Kollegen (@Ando) gefolgt. Behat bietet die Möglichkeit, mittels eines ContextInitializer die Kontexte vor Benutzung nochmal vorzubereiten. Dieser Initializer muss mit einer Extension angelegt und über die Konfiguration verknüpft werden.

Mittels dieses Konstruktes ist es möglich, einen beliebigen DI-Container (Zend, Symfony, Laravel, oder was auch immer) zu erzeugen und über den ContextInitializer an die Kontexte übergeben.

Es würde also nach Erzeugung des FeatureContext (und somit bevor es im Test benutzt wird) die Methode setDIContainer aufgerufen, die dem Context den DI-Container übergibt, und aus dem man sich dann z.B. den JiraIssueContainer holen kann.

Das funktioniert, ist aber viel Code, und vor allem bekommt der Kontext statt der benötigten Abhängigkeiten den DI-Container. Das ist aus mehreren guten Gründen ein Konstrukt, von dem man ja inzwischen auch in Zend abgekommen ist. Anstatt den kompletten Container zu übergeben sollte eine Klasse nur genau die Abhängigkeiten bekommen, die sie benötigt.

Beim Debuggen durch Behat hatte ich bereits gesehen, dass Kontexten Argumente übergeben werden können, und bei FriendsOfBehat gibt es auch eine Extension, die eine externe Container-Spezifikation im Symfony-Format laden kann. Allerdings erlaubt diese nur das Laden von Parametern, nicht von Services, und bei meinen Versuchen das nachzubauen kam ich auch nicht weiter.

Der Groschen fiel, als ich über diesen Code in der ServicesResolverFactory gestolpert bin:

public function createArgumentResolvers(Environment $environment)
    {
        $suite = $environment->getSuite();

        if (!$suite->hasSetting('services')) {
            return array();
        }

        $container = $this->createContainer($suite->getSetting('services'));
        $autowire = $suite->hasSetting('autowire') && $suite->getSetting('autowire');

        if ($environment instanceof ServiceContainerEnvironment) {
            $environment->setServiceContainer($container);
        }

        return $this->createResolvers($container, $autowire);
    }

Ah, man kann der Suite also irgendwas unter services anhängen? Das erzeugt dann wohl einen ServiceContainer…

Das Ergebnis: Behat erlaubt es unterhalb der Suite einen im Symfony-Format definierten Container zu definieren:

default:
  autoload:
    'Test': src/
  suites:
    default:
      path: features
      contexts:
        - \Test\Context\InitializerContext:
          - "../../../config/"
        - \Test\Context\JiraFeatureContext:
          - "@test.fixture.jira"
        - \Test\Context\CommandFeatureContext:
          - "@test.fixture.jira"
          - "@test.fixture.bitbucket"
        - \Test\Context\BitbucketFeatureContext:
          - "@test.fixture.bitbucket"

      services:
        test.fixture.jira:
          class: \Test\Fixture\JiraIssueContainer

        test.fixture.bitbucket:
          class: \Test\Fixture\BitbucketPullRequestContainer

Hier kann ich also unter „services:“ meine Klassen definieren und mit einer ID (z.B. test.fixtures.jira) versehen. Und in den Kontexten kann ich mittels „@test.fixtures.jira“ dann einfach diese Abhängigkeiten injizieren. Der JiraFeatureContext hat also danach einen Konstruktor:

/**
     * JiraFeatureContext constructor
     *
     * @param JiraIssueContainer $jiraIssueContainer A container to collect the fixture issues in
     *
     * @return void
     */
    public function __construct(JiraIssueContainer $jiraIssueContainer)
    {
        parent::__construct();
        $this->jiraIssueContainer = $jiraIssueContainer;
    }

Es kann so einfach gehen! Also weg mit dem Singleton-Code, jetzt könnte ich sogar Tests für die Test-Objekte schreiben!

Aufräumen

Außerdem war bisher unschön, dass in Gherkin auf den Namen des customfield verwiesen wurde. Das ist Konfiguration, und gehört genau dort hin. Somit habe ich den Sentence umbenannt:

Then I expect the issue "PVKZU-123" to have the field "customfield_11600" with the content "---" 
   wird nun zu
Then I expect the issue "PVKZU-123" to have the PRState "---"

Dazu muss ich die Konfiguration der Applikation laden. Diese liegt natürlich nicht im Test-Verzeichnis, und somit muss ich den Pfad zur Anwendungs-Konfiguration ebenfalls einstellen. Da das thematisch in keinen der bestehenden Kontexte passt, lege ich einen neuen Kontext „InitializerContext“ an, dem ich in der behat.yml den relativen Pfad zur Config übergebe.

contexts:
        - \Test\Context\InitializerContext:
          - "../../../config/"

Das wird ebenso in den Konstruktor übergeben, und dort wird die Konfiguration direkt geladen. Da DotEnv anders als bei Zend kein Config-Objekt nutzt, das durchgereicht werden muss, können wir die Abhängigkeiten klein halten. DotEnv wird einfach in Environment-Variablen übersetzt, die man mittels der PHP-Funktion getenv($key) abrufen kann. Architektonisch ist das ein wenig fragwürdig, aber in diesem Fall zweifellos praktisch.

Fazit

Durch diese Erweiterung ist das eine schöne runde Sache, wir werden in Zukunft wesentliche Teile unserer Integrationtests in Gherkin verfassen, und auch bestehende, schwer zu lesende und verstehende Tests umstellen. Die Tests können jetzt auch vom Produktmanagement abgenommen werden! Und da Entwickler bekanntermaßen schlechte Tester sind, ist allein das schon ein Grund, warum diese Schreibweise ein riesiger Fortschritt ist.

Wie immer: Feedback hierzu interessiert mich sehr, einfach unten kommentieren oder mich per Mail oder persönlich ansprechen!

About the author

IT-Teamleiter bei CHECK24.
Hasst es, Dinge selber zu tun, die auch automatisch gehen.
Will nie wieder Code von Hand formatieren müssen.
Will schon beim Schreiben merken, wenn er Blödsinn macht, nicht erst im Fehlerlog nach dem Deployment.

Schreibe einen Kommentar

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

Durch die weitere Nutzung der Seite stimmst du der Verwendung von Cookies zu. Weitere Informationen

Die Cookie-Einstellungen auf dieser Website sind auf "Cookies zulassen" eingestellt, um das beste Surferlebnis zu ermöglichen. Wenn du diese Website ohne Änderung der Cookie-Einstellungen verwendest oder auf "Akzeptieren" klickst, erklärst du sich damit einverstanden.

Schließen