Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Laravel

Laravel, BDD und Sie: Das erste Feature

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Laravel, BDD And You.
Laravel, BDD and You: Let’s Get Started

German (Deutsch) translation by Federicco Ancie (you can also view the original English article)

Im zweiten Teil dieser Reihe mit den Namen Laravel, BDD und You werden wir beginnen, unser erstes Feature mit Behat und PhpSpec zu beschreiben und zu erstellen. Im letzten Artikel haben wir alles eingerichtet und gesehen, wie einfach wir in unseren Behat-Szenarien mit Laravel interagieren können.

Kürzlich hat der Schöpfer von Behat, Konstantin Kudryashov (a.k.a. everzet), einen wirklich großartigen Artikel mit dem Titel Introducing Modeling by Example geschrieben. Der Workflow, den wir beim Erstellen unserer Funktion verwenden werden, ist stark von dem von everzet vorgestellten inspiriert.

Kurz gesagt, wir werden dieselbe .feature verwenden, um sowohl unsere Kerndomäne als auch unsere Benutzeroberfläche zu entwerfen. Ich hatte oft das Gefühl, dass meine Funktionen in meinen Akzeptanz- / Funktions- und Integrationssuiten stark dupliziert wurden. Als ich den Vorschlag von everzet las, dasselbe Feature für mehrere Kontexte zu verwenden, hat alles für mich geklickt und ich glaube, es ist der richtige Weg.

In unserem Fall haben wir unseren funktionalen Kontext, der vorerst auch als Akzeptanzschicht dient, und unseren Integrationskontext, der unsere Domäne abdeckt. Wir beginnen mit dem Aufbau der Domain und fügen anschließend die Benutzeroberfläche und die Framework-spezifischen Dinge hinzu.

Kleine Refactorings

Um den Ansatz "Gemeinsame Funktion, mehrere Kontexte" verwenden zu können, müssen wir einige Umgestaltungen unseres vorhandenen Setups vornehmen.

Zunächst werden wir die Begrüßungsfunktion löschen, die wir im ersten Teil erstellt haben, da wir sie nicht wirklich benötigen und sie nicht wirklich dem generischen Stil folgt, den wir benötigen, um mehrere Kontexte zu verwenden.

Zweitens werden wir unsere Features im Stammverzeichnis des features-Ordners haben, damit wir das path attribut aus unserer Datei behat.yml entfernen können. Wir werden auch den LaravelFeatureContext in FunctionalFeatureContext umbenennen (denken Sie daran, auch den Klassennamen zu ändern):

Schließlich, nur um die Dinge ein wenig aufzuräumen, denke ich, wir sollten alle Laravel-bezogenen Dinge in ihre eigene Eigenschaft bringen:

Im FunctionalFeatureContext können wir dann das Merkmal verwenden und die Dinge löschen, die wir gerade verschoben haben:

Eigenschaften sind eine großartige Möglichkeit, Ihre Kontexte zu bereinigen.

Teilen einer Funktion

Wie in Teil 1 dargestellt, werden wir eine kleine Anwendung für die Zeiterfassung erstellen. Die erste Funktion besteht darin, die Zeit zu verfolgen und aus den verfolgten Einträgen ein Arbeitszeitblatt zu erstellen. Hier ist die Funktion:

Denken Sie daran, dass dies nur ein Beispiel ist. Ich finde es einfacher, Features im wirklichen Leben zu definieren, da Sie ein tatsächlich zu lösendes Problem haben und häufig die Möglichkeit haben, das Feature mit Kollegen, Kunden oder anderen Stakeholdern zu diskutieren.

Okay, lassen Sie Behat die Szenario-Schritte für uns generieren:

Wir müssen die generierten Schritte nur ein kleines bisschen optimieren. Wir brauchen nur vier Schritte, um das Szenario abzudecken. Das Endergebnis sollte ungefähr so aussehen:

Unser funktionaler Kontext ist jetzt einsatzbereit, aber wir benötigen auch einen Kontext für unsere Integrationssuite. Zuerst fügen wir die Suite der Datei behat.yml hinzu:

Als nächstes können wir einfach den Standard-FeatureContext kopieren:

Denken Sie daran, den Klassennamen in IntegrationFeatureContext zu ändern und die use-Anweisung für die PendingException zu kopieren.

Da wir die Funktion gemeinsam nutzen, können wir die vier Schrittdefinitionen einfach aus dem Funktionskontext kopieren. Wenn Sie Behat ausführen, werden Sie feststellen, dass die Funktion zweimal ausgeführt wird: einmal für jeden Kontext.

Entwerfen der Domain

An diesem Punkt sind wir bereit, die ausstehenden Schritte in unserem Integrationskontext auszufüllen, um die Kerndomäne unserer Anwendung zu entwerfen. Der erste Schritt ist Gegeben, ich habe die folgenden Zeiteinträge, gefolgt von einer Tabelle mit Zeiteintragsdatensätzen. Um es einfach zu halten, lassen Sie uns einfach die Zeilen der Tabelle durchlaufen, versuchen, einen Zeiteintrag für jeden von ihnen zu instanziieren und sie einem Eintragsarray im Kontext hinzuzufügen:

Das Ausführen von Behat führt zu einem schwerwiegenden Fehler, da die TimeTracker\TimeEntry-Klasse noch nicht vorhanden ist. Hier betritt PhpSpec die Bühne. Am Ende wird TimeEntry eine eloquente Klasse sein, auch wenn wir uns noch keine Sorgen machen. PhpSpec und ORMs wie Eloquent spielen nicht so gut zusammen, aber wir können trotzdem PhpSpec verwenden, um die Klasse zu generieren und sogar ein grundlegendes Verhalten festzulegen. Verwenden wir die PhpSpec-Generatoren, um die TimeEntry-Klasse zu generieren:

Nachdem die Klasse generiert wurde, müssen wir den Abschnitt zum automatischen Laden unserer Datei composer.json aktualisieren:

Und natürlich composer dump-autoload ausführen.

Das Ausführen von PhpSpec gibt uns grün. Running Behat gibt uns auch grün. Was für ein toller Start!

Lassen Sie sich von Behat leiten, wie wäre es, wenn wir sofort zum nächsten Schritt übergehen: Wenn ich das Arbeitszeitblatt erstelle?

Das Schlüsselwort hier ist "generieren", was wie ein Begriff aus unserer Domain aussieht. In der Welt eines Programmierers kann die Übersetzung von "Arbeitszeittabelle generieren" in Code bedeuten, dass eine Arbeitszeittabellenklasse mit einer Reihe von TimeSheet instanziiert wird. Es ist wichtig zu versuchen, sich an die Sprache der Domain zu halten, wenn wir unseren Code entwerfen. Auf diese Weise hilft unser Code dabei, das beabsichtigte Verhalten unserer Anwendung zu beschreiben.

Ich identifiziere den Begriff generate als wichtig für die Domäne, weshalb ich denke, dass wir eine statische Generierungsmethode für eine TimeSheet-Klasse haben sollten, die als Alias für den Konstruktor dient. Diese Methode sollte eine Sammlung von Zeiteinträgen aufnehmen und auf dem Arbeitszeitblatt speichern.

Anstatt nur ein Array zu verwenden, ist es meiner Meinung nach sinnvoll, die mit Laravel gelieferte Klasse Illuminate\Support\Collection zu verwenden. Da TimeEntry ein eloquentes Modell sein wird, erhalten wir bei der Abfrage der Datenbank nach Zeiteinträgen ohnehin eine dieser Laravel-Sammlungen. Wie wäre es mit so etwas:

TimeSheet wird übrigens keine eloquente Klasse sein. Zumindest für den Moment müssen wir nur dafür sorgen, dass die Zeiteinträge bestehen bleiben, und dann werden die Arbeitszeitnachweise nur aus den Einträgen generiert.

Das Ausführen von Behat führt erneut zu einem schwerwiegenden Fehler, da TimeSheet nicht vorhanden ist. PhpSpec kann uns dabei helfen, Folgendes zu lösen:

Nach dem Erstellen der Klasse wird immer noch ein schwerwiegender Fehler angezeigt, da die statische Methode generate() immer noch nicht vorhanden ist. Da dies eine wirklich einfache statische Methode ist, glaube ich nicht, dass eine Spezifikation erforderlich ist. Es ist nichts weiter als ein Wrapper für den Konstruktor:

Dadurch wird Behat wieder grün, aber PhpSpec quietscht uns jetzt an und sagt: Argument 1, das an TimeTracker\TimeSheet::__construct() übergeben wurde, muss eine Instanz von Illuminate\Support\Collection sein, keine Angabe. Wir können dies lösen, indem wir eine einfache let() -Funktion schreiben, die vor jeder Spezifikation aufgerufen wird:

Dadurch werden wir auf der ganzen Linie wieder grün. Die Funktion stellt sicher, dass das Arbeitszeitblatt immer mit einem Modell der Collection-Klasse erstellt wird.

Wir können jetzt sicher zum Schritt Dann meine gesamte Zeit für... übergehen. Wir benötigen eine Methode, die einen Aufgabennamen verwendet und die akkumulierte Dauer aller Einträge mit diesem Aufgabennamen zurückgibt. Direkt von Gurke in Code übersetzt, könnte dies so etwas wie totalTimeSpentOn($task) sein:

Die Methode existiert nicht, daher führt das Ausführen von Behat zum Aufruf der undefinierten Methode TimeTracker\TimeSheet::totalTimeSpentOn().

Um die Methode zu spezifizieren, werden wir eine Spezifikation schreiben, die irgendwie ähnlich aussieht wie das, was wir bereits in unserem Szenario haben:

Beachten Sie, dass wir für die Instanzen TimeEntry und Collection keine Mocks verwenden. Dies ist unsere Integrationssuite, und ich glaube nicht, dass es notwendig ist, dies zu verspotten. Die Objekte sind recht einfach und wir möchten sicherstellen, dass die Objekte in unserer Domäne so interagieren, wie wir es erwarten. Es gibt wahrscheinlich viele Meinungen dazu, aber das macht für mich Sinn.

Weiter gehen:

Um die Einträge zu filtern, können wir die filter() -Methode für die Collection-Klasse verwenden. Eine einfache Lösung, die uns zum Grün bringt:

Unsere Spezifikation ist grün, aber ich bin der Meinung, dass wir hier von einigen Umgestaltungen profitieren könnten. Die Methode scheint zwei verschiedene Dinge zu tun: Einträge filtern und die Dauer akkumulieren. Lassen Sie uns letzteres nach seiner eigenen Methode extrahieren:

PhpSpec ist immer noch grün und wir haben jetzt drei grüne Schritte in Behat. Der letzte Schritt sollte einfach zu implementieren sein, da er dem gerade durchgeführten Schritt etwas ähnlich ist.

Wenn Sie Behat ausführen, rufen Sie die undefinierte Methode TimeTracker\TimeSheet::totalTimeSpent() auf. Wie wäre es, wenn wir in unserer Spezifikation kein separates Beispiel für diese Methode erstellen und es einfach zu dem hinzufügen, das wir bereits haben? Es stimmt vielleicht nicht ganz mit dem überein, was "richtig" ist, aber lassen Sie uns ein wenig pragmatisch sein:

Lassen Sie PhpSpec die Methode generieren:

Mit der sumDuration() -Methode ist es jetzt einfach, grün zu werden:

Und jetzt haben wir eine grüne Funktion. Unsere Domain entwickelt sich langsam weiter!

Gestaltung der Benutzeroberfläche

Jetzt ziehen wir in unsere Funktionssuite um. Wir werden die Benutzeroberfläche entwerfen und uns mit allen Laravel-spezifischen Dingen befassen, die nicht das Anliegen unserer Domain sind.

Während der Arbeit in der Funktionssuite können wir das Flag -s hinzufügen, um Behat anzuweisen, unsere Funktionen nur über den FunctionalFeatureContext auszuführen:

Der erste Schritt sieht ähnlich aus wie der erste im Integrationskontext. Anstatt nur dafür zu sorgen, dass die Einträge im Kontext eines Arrays bestehen bleiben, müssen sie tatsächlich in einer Datenbank bestehen bleiben, damit sie später abgerufen werden können:

Das Ausführen von Behat führt zu einem schwerwiegenden Fehler. Rufen Sie die undefinierte Methode TimeTracker\TimeEntry::save() auf, da TimeEntry immer noch kein eloquentes Modell ist. Das ist leicht zu beheben:

Wenn wir Behat erneut ausführen, beschwert sich Laravel, dass keine Verbindung zur Datenbank hergestellt werden kann. Wir können dies beheben, indem wir dem Verzeichnis app/config/testing eine Datei database.php hinzufügen, um die Verbindungsdetails für unsere Datenbank hinzuzufügen. Bei größeren Projekten möchten Sie wahrscheinlich denselben Datenbankserver für Ihre Tests und Ihre Produktionscodebasis verwenden. In unserem Fall verwenden wir jedoch nur eine SQLite-Datenbank im Speicher. Dies ist mit Laravel sehr einfach einzurichten:

Wenn wir nun Behat ausführen, wird uns mitgeteilt, dass es keine time_entries-Tabelle gibt. Um dies zu beheben, müssen wir eine Migration durchführen:

Wir sind immer noch nicht grün, da wir eine Möglichkeit benötigen, Behat anzuweisen, unsere Migrationen vor jedem Szenario durchzuführen, sodass wir jedes Mal eine saubere Tafel haben. Mithilfe der Anmerkungen von Behat können wir diese beiden Methoden zum Merkmal LaravelTrait hinzufügen:

Das ist ziemlich ordentlich und bringt unseren ersten Schritt zum Grün.

Als nächstes folgt der Schritt Wenn ich das Arbeitszeitblatt generiere. So wie ich es sehe, entspricht das Generieren des Arbeitszeitblatts dem Aufrufen der index aktion der Zeiteintragsressource, da das Arbeitszeitblatt die Sammlung aller Zeiteinträge ist. Das Arbeitszeitblattobjekt ist also wie ein Container für alle Zeiteinträge und bietet uns eine gute Möglichkeit, Einträge zu verarbeiten. Anstatt zu /time-entries zu gehen, sollte der Mitarbeiter, um das Arbeitszeitblatt zu sehen, zu /time-sheet gehen. Wir sollten das in unsere Schrittdefinition aufnehmen:

Dies führt zu einer NotFoundHttpException, da die Route noch nicht definiert ist. Wie ich gerade erklärt habe, sollte diese URL der index aktion für die Zeiteintragsressource zugeordnet werden:

Um grün zu werden, müssen wir den Controller generieren:

Und los geht's.

Schließlich müssen wir die Seite crawlen, um die Gesamtdauer der Zeiteinträge zu ermitteln. Ich gehe davon aus, dass wir eine Art Tabelle haben werden, die die Dauer zusammenfasst. Die letzten beiden Schritte sind so ähnlich, dass wir sie nur gleichzeitig implementieren werden:

Der Crawler sucht im letzten Beispiel nach einem <td> -Knoten mit der ID [task_name]TotalDuration oder totalDuration.

Da wir immer noch keine Ansicht haben, teilt uns der Crawler mit, dass die aktuelle Knotenliste leer ist.

Um dies zu beheben, erstellen wir die index aktion. Zuerst holen wir die Sammlung von Zeiteinträgen. Zweitens generieren wir aus den Einträgen ein Arbeitszeitblatt und senden es an die (noch nicht vorhandene) Ansicht.

Die Ansicht besteht vorerst nur aus einer einfachen Tabelle mit den zusammengefassten Dauerwerten:

Wenn Sie Behat erneut ausführen, werden Sie feststellen, dass wir die Funktion erfolgreich implementiert haben. Vielleicht müssen wir uns einen Moment Zeit nehmen, um festzustellen, dass wir nicht einmal einen Browser geöffnet haben! Das ist eine riesige Verbesserung unseres Workflows. Als netten Bonus haben wir jetzt automatisierte Tests für unsere Anwendung. Cool!

Abschluss

Wenn Sie vendor/bin/behat ausführen, um beide Behat-Suiten auszuführen, werden Sie feststellen, dass beide jetzt grün sind. Wenn Sie PhpSpec ausführen, werden Sie leider feststellen, dass unsere Spezifikationen fehlerhaft sind. Wir erhalten einen schwerwiegenden Fehler. Klasse 'Eloquent' nicht gefunden in.... Dies liegt daran, dass Eloquent ein Alias ist. Wenn Sie in app/config/app.php unter Aliase nachsehen, werden Sie feststellen, dass Eloquent tatsächlich ein Alias für Illuminate\Database\Eloquent\Model ist. Um PhpSpec wieder grün zu machen, müssen wir diese Klasse importieren:

Wenn Sie diese beiden Befehle ausführen:

Sie werden sehen, dass wir sowohl mit Behat als auch mit PhpSpec wieder grün sind. Cool!

Wir haben jetzt unsere erste Funktion beschrieben und entworfen, die vollständig einen BDD-Ansatz verwendet. Wir haben gesehen, wie wir vom Entwerfen der Kerndomäne unserer Anwendung profitieren können, bevor wir uns um die Benutzeroberfläche und die Framework-spezifischen Dinge kümmern. Wir haben auch gesehen, wie einfach es ist, mit Laravel und insbesondere der Datenbank in unseren Behat-Kontexten zu interagieren.

Im nächsten Artikel werden wir viel umgestalten, um zu viel Logik bei unseren Eloquent-Modellen zu vermeiden, da diese schwieriger isoliert zu testen sind und eng mit Laravel verbunden sind. Bleiben Sie dran!

Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.