Laravel, BDD und Du Das erste Feature

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

Vor kurzem schrieb der Schöpfer von Behat, Konstantin Kudryashov (alias Everzet), einen wirklich großartigen Artikel mit dem Namen Introducing Modeling by Example. Der Workflow, den wir beim Erstellen unserer Funktion verwenden werden, ist in hohem Maße von dem von everzet vorgestellten inspiriert. 

Kurz, wir werden dasselbe verwenden .Merkmal um sowohl unsere Kerndomäne als auch unsere Benutzeroberfläche zu gestalten. Ich hatte oft das Gefühl, dass meine Features in meinen Akzeptanz-, Funktions- und Integrationssuiten vielfach dupliziert wurden. Als ich den Vorschlag von Everzet zur Verwendung derselben Funktion für mehrere Kontexte las, klickte das alles für mich und ich glaube, es ist der Weg zu gehen. 

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 Erstellen der Domäne und fügen anschließend die für die Benutzeroberfläche und das Framework spezifischen Elemente hinzu.

Kleine Refactorings

Um den Ansatz "Gemeinsam genutztes Feature, mehrere Kontexte" zu verwenden, müssen wir einige Umgestaltungen unserer bestehenden Einrichtung vornehmen.

Zunächst werden wir das Willkommens-Feature löschen, das wir im ersten Teil gemacht haben, da wir es nicht wirklich brauchen und es nicht wirklich dem generischen Stil folgt, den wir benötigen, um mehrere Kontexte zu verwenden.

$ git rm Funktionen / funktional / welcome.feature

Zweitens werden wir unsere Eigenschaften in der Wurzel von haben Eigenschaften Ordner, so können wir den Ordner entfernen Pfad Attribut von unserem behat.yml Datei. Wir werden auch die umbenennen LaravelFeatureContext zu FunctionalFeatureContext (Denken Sie daran, auch den Klassennamen zu ändern):

Standard: Suites: Funktional: Kontexte: [FunctionalFeatureContext]

Um die Dinge ein wenig zu bereinigen, sollten wir alle Laravel-bezogenen Dinge in die eigene Eigenschaft bringen:

# features / bootstrap / LaravelTrait.php app) $ this-> refreshApplication ();  / ** * Erstellt die Anwendung. * * @return \ Symfony \ Component \ HttpKernel \ HttpKernelInterface * / public function createApplication () $ unitTesting = true; $ testEnvironment = 'testing'; Rückkehr erforderlich __DIR __. '/… /… /bootstrap/start.php';  

In dem FunctionalFeatureContext Wir können dann das Merkmal verwenden und die Dinge entfernen, die wir gerade verschoben haben:

/ ** * Behat Kontextklasse. * / class FunctionalFeatureContext implementiert SnippetAcceptingContext use LaravelTrait; / ** * Initialisiert den Kontext. * * Jedes Szenario erhält ein eigenes Kontextobjekt. * Sie können beliebige Argumente über behat.yml an den Kontextkonstruktor übergeben. * / public function __construct () 

Merkmale sind eine großartige Möglichkeit, um Ihre Kontexte aufzuräumen.

Eine Funktion freigeben

Wie in Teil 1 vorgestellt, werden wir eine kleine Anwendung für die Zeiterfassung erstellen. Beim ersten Feature geht es darum, die Zeit zu verfolgen und aus den erfassten Einträgen ein Arbeitszeitblatt zu erstellen. Hier ist die Funktion:

Funktion: Zeit erfassen Um die für Aufgaben aufgewendete Zeit zu verfolgen Als Mitarbeiter muss ich ein Arbeitszeitblatt mit Zeiteinträgen verwalten Szenario: Arbeitszeitblatt erstellen Vorausgesetzt, ich habe die folgenden Zeiteinträge | Aufgabe | Dauer | | Kodierung | 90 | | Kodierung | 30 | | dokumentieren | 150 | Wenn ich das Arbeitszeitblatt generiere, sollte meine Gesamtzeit für das Codieren 120 Minuten betragen. Und meine Gesamtzeit für die Dokumentation sollte 150 Minuten betragen. Und meine Gesamtzeit für Meetings sollte 0 Minuten betragen. Und meine Gesamtzeit sollte 270 Minuten betragen 

Denken Sie daran, dass dies nur ein Beispiel ist. Ich finde es einfacher, Features im realen Leben zu definieren, da Sie ein aktuelles Problem haben, das Sie lösen müssen, und oft die Möglichkeit haben, das Feature mit Kollegen, Kunden oder anderen Interessengruppen zu besprechen.

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

$ vendor / bin / behat --dry-run --append-snippets

Wir müssen die generierten Schritte nur ein klein wenig anpassen. Wir brauchen nur vier Schritte, um das Szenario abzudecken. Das Endergebnis sollte ungefähr so ​​aussehen:

/ ** * @Given Ich habe die folgenden Zeiteinträge. * / Public function iHaveTheFollowingTimeEntries (TableNode $ table) throw new PendingException ();  / ** * @Wenn ich das Arbeitszeitblatt generiere * / public function iGenerateTheTimeSheet () throw new PendingException ();  / ** * @Dann sollte meine Gesamtzeit für: Task Folgendes sein: expectedDuration Minuten * / public function myTotalTimeSpentOnTaskShouldBeMinutes ($ task, $ expectedDuration) neue PendingException () auslösen  / ** * @Dann sollte meine Gesamtzeit folgendermaßen aussehen: expectedDuration Minuten * / public function myTotalTimeSpentShouldBeMinutes ($ expectedDuration) throw new PendingException ();  

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

Standard: Suites: Funktional: Kontexte: [FunctionalFeatureContext] Integration: Kontexte: [IntegrationFeatureContext] 

Als Nächstes können wir den Standardwert einfach kopieren FeatureContext:

$ cp features / bootstrap / FeatureContext.php features / bootstrap / IntegrationFeatureContext.php 

Vergessen Sie nicht, den Klassennamen in zu ändern IntegrationFeatureContext und auch die Nutzungserklärung für die PendingException.

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

Entwerfen der Domäne

An dieser Stelle können wir die anstehenden Schritte in unserem Integrationskontext ausfüllen, um die Kerndomäne unserer Anwendung zu entwerfen. Der erste Schritt ist Dazu habe ich folgende Zeiteinträge, gefolgt von einer Tabelle mit Zeiteintragsdatensätzen. Um es einfach zu halten, lassen Sie uns einfach die Zeilen der Tabelle durchlaufen, versuchen Sie, einen Zeiteintrag für jede von ihnen zu instanziieren, und fügen Sie sie einem Eintragsfeld im Kontext hinzu:

use TimeTracker \ TimeEntry;… / ** * @Given Ich habe die folgenden Zeiteinträge. * / public function iHaveTheFollowingTimeEntries (TableNode $ table) $ this-> entries = []; $ rows = $ table-> getHash (); foreach ($ row as $ row) $ entry = new TimeEntry; $ entry-> task = $ row ['task']; $ entry-> duration = $ row ['duration']; $ this-> entries [] = $ entry;  

Wenn Sie Behat ausführen, wird ein schwerwiegender Fehler verursacht TimeTracker \ TimeEntry Klasse existiert noch nicht. Hier tritt PhpSpec auf die Bühne. Schlussendlich, TimeEntry wird eine eloquente Klasse sein, auch wenn wir uns noch nicht darum kümmern. PhpSpec und ORMs wie Eloquent spielen nicht so gut zusammen, aber wir können immer noch PhpSpec verwenden, um die Klasse zu generieren und sogar ein paar grundlegende Verhaltensweisen aufzuzeigen. Lassen Sie uns die PhpSpec-Generatoren verwenden, um die TimeEntry Klasse:

$ vendor / bin / phpspec desc "TimeTracker \ TimeEntry" $ vendor / bin / phpspec run Soll ich "TimeTracker \ TimeEntry" für Sie erstellen? y 

Nachdem die Klasse generiert wurde, müssen wir den Autoload-Abschnitt von unserem aktualisieren composer.json Datei:

"autoload": "classmap": ["App / Befehle", "App / Controller", "App / Modelle", "App / Datenbank / Migrationen", "App / Datenbank / Seeds"], "psr-4" : "TimeTracker \\": "src / TimeTracker", 

Und natürlich laufen composer dump-autoload.

Der Betrieb von PhpSpec gibt uns grün. Laufen Behat gibt uns auch grün. Was für ein toller Start!

Lassen Sie sich von Behat leiten, wie gehen wir zum nächsten Schritt über, Wenn ich das Arbeitszeitblatt generiere, jetzt sofort?

Das Schlüsselwort hier ist "generieren", was wie ein Begriff aus unserer Domain aussieht. In der Welt eines Programmierers könnte das Übersetzen von "Generieren der Arbeitszeittabelle" in Code lediglich die Instanziierung von a bedeuten TimeSheet Klasse mit einer Reihe von Zeiteinträgen. Beim Entwerfen unseres Codes ist es wichtig, sich an die Sprache der Domäne zu halten. Auf diese Weise beschreibt unser Code das beabsichtigte Verhalten unserer Anwendung. 

Ich identifiziere den Begriff generieren so wichtig für die Domain, weshalb ich denke, wir sollten eine statische Generierungsmethode auf einer haben TimeSheet Klasse, die dem Konstruktor als Alias ​​dient. Diese Methode sollte eine Sammlung von Zeiteinträgen benötigen und diese auf dem Arbeitszeitblatt speichern. 

Anstatt nur ein Array zu verwenden, ist es meiner Meinung nach sinnvoll, das Array zu verwenden Illuminate \ Support \ Collection Klasse, die mit Laravel kommt. Schon seit TimeEntry wird ein eloquentes Modell sein, wenn wir die Datenbank nach Zeiteinträgen abfragen, erhalten wir sowieso eine dieser Laravel-Sammlungen. Wie wäre es mit so etwas:

benutze Illuminate \ Support \ Collection; Verwenden Sie TimeTracker \ TimeSheet; use TimeTracker \ TimeEntry;… / ** * @Wenn ich das Arbeitszeitblatt generiere * / public function iGenerateTheTimeSheet () $ this-> sheet = TimeSheet :: generate (Collection :: make ($ this-> entries));  

TimeSheet wird übrigens keine Eloquent-Klasse sein. Zumindest für den Moment müssen wir nur die Zeiteinträge beibehalten, und dann werden nur noch die Arbeitszeitnachweise erstellt generiert aus den Einträgen.

Wenn Sie Behat ausführen, wird erneut ein schwerwiegender Fehler verursacht, weil TimeSheet ist nicht vorhanden. PhpSpec kann uns helfen, das zu lösen:

$ vendor / bin / phpspec desc "TimeTracker \ TimeSheet" $ vendor / bin / phpspec run Soll ich "TimeTracker \ TimeSheet" für Sie erstellen? y $ vendor / bin / phpspec $ vendor / bin / behat ausführen PHP Schwerwiegender Fehler: Aufruf der nicht definierten Methode TimeTracker \ TimeSheet :: generate () 

Nach dem Erstellen der Klasse wird immer noch ein schwerwiegender Fehler angezeigt, da dies statisch ist generieren() Methode existiert noch nicht. 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:

Einträge = $ Einträge;  public static function generate (Collection $ entries) neues statisches zurückgeben ($ entries);  

Dadurch wird Behat wieder grün, aber PhpSpec quietscht jetzt und sagt: Argument 1, das an TimeTracker \ TimeSheet :: __ construct () übergeben wird, muss eine Instanz von Illuminate \ Support \ Collection sein, keines angegeben. Wir können das lösen, indem wir ein einfaches schreiben Lassen() Funktion, die vor jeder Spezifikation aufgerufen wird:

put (neues TimeEntry); $ this-> beConstructedWith ($ entries);  function it_is_initializable () $ this-> shouldHaveType ('TimeTracker \ TimeSheet');  

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

Wir können jetzt sicher in die Dann meine Gesamtzeit auf…  Schritt. Wir benötigen eine Methode, die einen Aufgabennamen übernimmt und die Gesamtdauer aller Einträge mit diesem Aufgabennamen zurückgibt. Direkt von der Gurke in den Code übersetzt, könnte dies ungefähr so ​​aussehen totalTimeSpentOn ($ task):

/ ** * @Dann sollte meine Gesamtzeit für: task Folgendes sein: erwartetDuration Minuten * / public function myTotalTimeSpentOnTaskShouldBeMinutes ($ task, $ expectedDuration) $ actualDuration = $ this-> sheet-> totalTimeSpentOn ($ task); PHPUnit :: assertEquals ($ expectedDuration, $ actualDuration);  

Die Methode existiert nicht, also gibt uns das Laufen von Behat Aufruf an nicht definierte Methode TimeTracker \ TimeSheet :: totalTimeSpentOn ().

Um die Methode auszuarbeiten, schreiben wir eine Spezifikation, die ähnlich aussieht wie in unserem Szenario:

Funktion it_should_calculate_total_time_spent_on_task () $ entry1 = new TimeEntry; $ entry1-> task = 'schlafend'; $ entry1-> duration = 120; $ entry2 = new TimeEntry; $ entry2-> task = 'eating'; $ entry2-> duration = 60; $ entry3 = new TimeEntry; $ entry3-> task = 'schlafend'; $ entry3-> duration = 120; $ collection = Collection :: make ([$ entry1, $ entry2, $ entry3]); $ this-> beConstructedWith ($ collection); $ this-> totalTimeSpentOn ('sleeping') -> shouldBe (240); $ this-> totalTimeSpentOn ('eating') -> shouldBe (60);  

Beachten Sie, dass wir keine Mocks für die verwenden TimeEntry und Sammlung Instanzen. Dies ist unsere Integrationssuite, und ich denke 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.

Weitergehen:

$ vendor / bin / phpspec run Soll "TimeTracker \ TimeSheet :: totalTimeSpentOn ()" für Sie erstellt werden? y $ vendor / bin / phpspec run 25 ✘ Es sollte die Gesamtzeit berechnet werden, die für die erwartete Aufgabe [Integer: 240] aufgewendet wurde, aber Null erhalten hat. 

Um die Einträge zu filtern, können wir die verwenden Filter() Methode auf der Sammlung Klasse. Eine einfache Lösung, die uns zum Grün bringt:

öffentliche Funktion totalTimeSpentOn ($ task) $ entries = $ this-> entries-> filter (function ($ entry) use ($ task) return $ entry-> task === $ task;); $ duration = 0; foreach ($ einträge als $ entry) $ duration + = $ entry-> duration;  return $ duration;  

Unsere Spezifikation ist grün, aber ich glaube, dass wir hier von Refactoring profitieren könnten. Die Methode scheint zwei verschiedene Dinge zu tun: Einträge filtern und die Dauer summieren. Lass uns die letztere zu ihrer eigenen Methode extrahieren:

öffentliche Funktion totalTimeSpentOn ($ task) $ entries = $ this-> entries-> filter (function ($ entry) use ($ task) return $ entry-> task === $ task;); $ this-> sumDuration ($ entries) zurückgeben;  geschützte Funktion sumDuration ($ entries) $ duration = 0; foreach ($ einträge als $ entry) $ duration + = $ entry-> duration;  return $ duration;  

PhpSpec ist immer noch grün und wir haben jetzt drei grüne Schritte in Behat. Der letzte Schritt sollte leicht zu implementieren sein, da er dem gerade beschriebenen Schritt ähnelt.

/ ** * @Dann sollte meine Gesamtzeit folgendermaßen aussehen: erwartetDuration Minuten * / public function myTotalTimeSpentShouldBeMinutes ($ expectedDuration) $ actualDuration = $ this-> sheet-> totalTimeSpent (); PHPUnit :: assertEquals ($ expectedDuration, $ actualDuration);  

Laufen Behat wird uns geben Aufruf an nicht definierte Methode TimeTracker \ TimeSheet :: totalTimeSpent (). Anstelle eines separaten Beispiels in unserer Spezifikation für diese Methode, fügen wir es einfach zu dem hinzu, das wir bereits haben. Es ist vielleicht nicht ganz im Einklang mit dem, was "richtig" zu tun ist, aber lassen Sie uns ein wenig pragmatisch sein:

… $ This-> beConstructedWith ($ collection); $ this-> totalTimeSpentOn ('sleeping') -> shouldBe (240); $ this-> totalTimeSpentOn ('eating') -> shouldBe (60); $ this-> totalTimeSpent () -> shouldBe (300); 

Lassen Sie PhpSpec die Methode generieren:

$ vendor / bin / phpspec run Soll "TimeTracker \ TimeSheet :: totalTimeSpent ()" für Sie erstellt werden? y $ vendor / bin / phpspec run 25 ✘ Es sollte die Gesamtzeit berechnet werden, die für die erwartete Aufgabe [Integer: 300] aufgewendet wurde, aber null erhalten hat. 

Zu grün zu gelangen ist jetzt einfach, da wir das haben sumDuration () Methode:

public function totalTimeSpent () return $ this-> sumDuration ($ this-> entries);  

Und jetzt haben wir ein grünes Feature. Unsere Domain entwickelt sich langsam!

Entwerfen der Benutzeroberfläche

Jetzt ziehen wir in unsere Funktionssuite. Wir werden die Benutzeroberfläche entwerfen und uns mit allen Laravel-spezifischen Dingen beschäftigen, die nicht in unserer Domäne liegen.

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

$ vendor / bin / behat -s funktional 

Der erste Schritt sieht ähnlich aus wie der erste des Integrationskontexts. Anstatt die Einträge nur für den Kontext in einem Array persistent zu machen, müssen wir sie tatsächlich in einer Datenbank persistent machen, damit sie später abgerufen werden können:

use TimeTracker \ TimeEntry;… / ** * @Given Ich habe die folgenden Zeiteinträge. * / public function iHaveTheFollowingTimeEntries (TableNode $ table) $ rows = $ table-> getHash (); foreach ($ row as $ row) $ entry = new TimeEntry; $ entry-> task = $ row ['task']; $ entry-> duration = $ row ['duration']; $ entry-> save ();  

Wenn Sie Behat ausführen, erhalten Sie einen schwerwiegenden Fehler Aufruf an nicht definierte Methode TimeTracker \ TimeEntry :: save (), schon seit TimeEntry ist immer noch kein eloquentes Modell. Das ist leicht zu beheben:

Namespace TimeTracker; Klasse TimeEntry erweitert \ Eloquent  

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

 'sqlite', 'connections' => array ('sqlite' => array ('driver' => 'sqlite', 'database' => ': speicher:', 'prefix' => ",),)) ; 

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

$ php Handwerker-Migration: make createTimeEntriesTable --create = "time_entries" 
Schema :: create ('time_entries', Funktion (Blueprint $ table) $ table-> increments ('id'); $ table-> string ('task'); $ table-> integer ('duration'); $ table-> timestamps ();); 

Wir sind immer noch nicht umweltfreundlich, da wir Behat anweisen müssen, unsere Migrationen vor jedem Szenario auszuführen, so dass wir jedes Mal eine saubere Sache haben. Mit den Annotationen von Behat können wir diese beiden Methoden zum hinzufügen LaravelTrait Merkmal:

/ ** * @BeforeScenario * / public function setupDatabase () $ this-> app ['artisan'] -> call ('migrate');  / ** * @AfterScenario * / public function cleanDatabase () $ this-> app ['artisan'] -> call ('migrate: reset');  

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

Als nächstes ist das Wenn ich das Arbeitszeitblatt generiere Schritt. So wie ich es sehe, ist das Erstellen des Arbeitszeitblattes das Äquivalent eines Besuchs im Index Aktion der Zeiteintragsressource, da das Arbeitszeitblatt die Erfassung aller Zeiteinträge ist. Das Time-Sheet-Objekt ist also ein Container für alle Zeiteinträge und gibt uns eine nette Möglichkeit, Einträge zu behandeln. Anstatt zu gehen / Zeiteinträge, Um das Arbeitszeitblatt zu sehen, denke ich, sollte der Mitarbeiter zu gehen / Arbeitszeitblatt. Wir sollten das in unsere Schrittdefinition aufnehmen:

/ ** * @Wenn ich das Arbeitszeitblatt generiere * / public function iGenerateTheTimeSheet () $ this-> call ('GET', '/ time-sheet'); $ this-> crawler = new Crawler ($ this-> client-> getResponse () -> getContent (), url ('/'));  

Dies führt dazu, dass a NotFoundHttpException, da die Route noch nicht definiert ist. Wie ich gerade erklärt habe, denke ich, sollte diese URL dem entsprechen Index Aktion für die Zeiteintragsressource:

Route :: get ('time-sheet', ['as' => 'time_sheet', 'verwendet' => 'TimeEntriesController @ index']); 

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

$ php Handwerker-Controller: TimeEntriesController $ composer dump-autoload erstellen 

Und da gehen wir hin.

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

/ ** * @Dann sollte meine Gesamtzeit für: task Folgendes sein: erwartetDuration Minuten * / public function myTotalTimeSpentOnTaskShouldBeMinutes ($ task, $ expectedDuration) $ actualDuration = $ this-> crawler-> filter ('td #'. $ Task 'TotalDuration') -> text (); PHPUnit :: assertEquals ($ expectedDuration, $ actualDuration);  / ** * @Dann sollte meine Gesamtzeit folgendermaßen aussehen: expectedDuration Minuten * / public function myTotalTimeSpentShouldBeMinutes ($ expectedDuration) $ actualDuration = $ this-> crawler-> filter ('td # totalDuration') -> text (); PHPUnit :: assertEquals ($ expectedDuration, $ actualDuration);  

Der Crawler sucht a  Knoten mit einer ID von [task_name] TotalDuration oder totalDuration im letzten Beispiel.

Da wir immer noch keine Sicht haben, wird uns der Crawler das sagen Die aktuelle Knotenliste ist leer.

Um dies zu beheben, lassen Sie uns die Index Aktion. Zuerst holen wir die Sammlung von Zeiteinträgen ab. Zweitens generieren wir aus den Einträgen ein Arbeitszeitblatt und senden es in die (noch nicht vorhandene) Sicht.

Verwenden Sie TimeTracker \ TimeSheet; Verwenden Sie TimeTracker \ TimeEntry; Klasse TimeEntriesController erweitert \ BaseController / ** * Zeigt eine Auflistung der Ressource an. * * @return Response * / public function index () $ entries = TimeEntry :: all (); $ sheet = TimeSheet :: generate ($ entries); return View :: make ('time_entries.index', compact ('sheet')); … 

Die Ansicht wird vorerst nur aus einer einfachen Tabelle mit den zusammengefassten Werten für die Dauer bestehen:

Arbeitszeitblatt

Aufgabe Gesamtdauer
Codierung $ sheet-> totalTimeSpentOn ('coding')
dokumentieren $ sheet-> totalTimeSpentOn ('documenting')
Sitzungen $ sheet-> totalTimeSpentOn ('Meetings')
Gesamt $ sheet-> totalTimeSpent ()

Wenn Sie Behat erneut ausführen, werden Sie feststellen, dass wir die Funktion erfolgreich implementiert haben. Vielleicht sollten wir uns einen Moment Zeit nehmen, um zu erkennen, dass wir nicht einmal einen Browser geöffnet haben! Dies ist eine massive Verbesserung unseres Workflows. Als netter Bonus haben wir jetzt automatisierte Tests für unsere Anwendung. Yay!

Fazit

Wenn du läufst Verkäufer / bin / behat Um beide Behat-Suiten auszuführen, werden Sie sehen, dass beide jetzt grün sind. Wenn Sie jedoch PhpSpec ausführen, werden Sie leider feststellen, dass unsere Spezifikationen defekt sind. Wir bekommen einen schwerwiegenden Fehler Klasse "Eloquent" nicht gefunden in… . Dies liegt daran, dass Eloquent ein Alias ​​ist. Wenn du mal reinschaust app / config / app.php unter Aliases wirst du das sehen Beredt ist eigentlich ein Alias ​​für Illuminate \ Database \ Eloquent \ Model. Um PhpSpec wieder auf Grün zu bringen, müssen wir diese Klasse importieren:

Namespace TimeTracker; benutze Illuminate \ Database \ Eloquent \ Model als Eloquent; Klasse TimeEntry erweitert Eloquent  

Wenn Sie diese beiden Befehle ausführen:

$ vendor / bin / phpspec run; Verkäufer / bin / behat 

Sie werden sehen, dass wir mit Behat und PhpSpec wieder grün sind. Yay! 

Wir haben nun unser erstes Feature beschrieben und entworfen, und zwar vollständig unter Verwendung eines BDD-Ansatzes. Wir haben gesehen, wie wir von der Gestaltung der Kerndomäne unserer Anwendung profitieren können, bevor wir uns um die Benutzeroberfläche und das spezifische Framework kümmern. Wir haben auch gesehen, wie einfach es ist, mit Laravel und insbesondere mit der Datenbank in unserem Behat-Kontext zu interagieren. 

Im nächsten Artikel werden wir viel Refactoring durchführen, um zu viel Logik bei unseren Eloquent-Modellen zu vermeiden, da diese isoliert schwieriger zu testen sind und eng mit Laravel gekoppelt sind. Bleib dran!