Advertisement
  1. Code
  2. Laravel

Laravel, BDD und Sie: Fangen wir an

Scroll to top
Read Time: 13 min
This post is part of a series called Laravel, BDD And You.
Laravel, BDD and You: The First Feature

() translation by (you can also view the original English article)

Willkommen zu dieser Reihe über die Entwicklung von Laravel-Anwendungen mithilfe eines BDD-Ansatzes (Behavioral Driven Development). Full-Stack-BDD kann kompliziert und einschüchternd wirken. Es gibt genauso viele Möglichkeiten wie Entwickler.

In dieser Serie werde ich Sie durch meinen Ansatz führen, Behat und PhpSpec zu verwenden, um eine Laravel-Anwendung von Grund auf neu zu entwerfen.

Es gibt im Allgemeinen viele Ressourcen zu BDD, aber Laravel-spezifisches Material ist schwer zu finden. Daher konzentrieren wir uns in dieser Reihe mehr auf die Laravel-bezogenen Aspekte und weniger auf die allgemeinen Dinge, die Sie über viele andere Orte lesen können.

Verhalten beschreiben

Bei der Beschreibung des Verhaltens, das auch als Schreiben von Geschichten und Spezifikationen bezeichnet wird, verwenden wir einen Outside-In-Ansatz.  Dies bedeutet, dass wir jedes Mal, wenn wir eine neue Funktion erstellen, mit dem Schreiben der gesamten User Story beginnen. Dies geschieht normalerweise aus Sicht der Kunden oder Stakeholder.

Was erwarten wir, wenn wir dies tun?

Wir dürfen keinen Code schreiben, bis wir beispielsweise einen fehlgeschlagenen roten Schritt haben, es sei denn, wir überarbeiten vorhandenen Code.

Manchmal ist es notwendig, einen kleinen Teil einer Geschichte oder eines Features (zwei Wörter, die ich austauschbar verwende), an dem wir arbeiten, iterativ zu lösen.  Dies bedeutet häufig das Schreiben von Spezifikationen mit PhpSpec. Manchmal dauert es viele Iterationen auf Integrations- oder Einheitenebene, bis die gesamte Geschichte (auf Akzeptanzebene) verstrichen ist.  Das klingt alles sehr kompliziert, ist es aber wirklich nicht. Ich bin ein großer Anhänger des Lernens durch Handeln, daher denke ich, dass alles sinnvoller sein wird, wenn wir anfangen, einen tatsächlichen Code zu schreiben.

Wir werden Geschichten und Spezifikationen auf vier verschiedenen Ebenen schreiben:

1. Akzeptanz

Meistens dient unsere Funktionssuite als Akzeptanzschicht. Die Art und Weise, wie wir unsere Funktionen in unserer Funktionssuite beschreiben, wird der Art und Weise, wie wir Akzeptanzgeschichten schreiben (unter Verwendung eines automatisierten Browser-Frameworks), sehr ähnlich sein und als solche eine Menge Duplikate erzeugen.

Solange die Geschichten das Verhalten aus Sicht des Kunden beschreiben, dienen sie als Akzeptanzgeschichten. Wir werden den Symfony DomCrawler verwenden, um die Ausgabe unserer Anwendung zu testen.  Später in der Serie werden wir möglicherweise feststellen, dass wir über einen tatsächlichen Browser testen müssen, der auch JavaScript ausführen kann.  Das Testen über den Browser bringt einige neue Probleme mit sich, da wir sicherstellen müssen, dass wir die Stundentestumgebung laden, wenn die Suite ausgeführt wird.

2. Funktionell

In unserer Funktionssuite haben wir Zugriff auf die Laravel-Anwendung, was sehr praktisch ist. Erstens macht es die Unterscheidung zwischen Umgebungen einfach.  Zweitens macht das Nicht-Durchsuchen eines Browsers unsere Testsuite viel schneller. Wann immer wir eine neue Funktion implementieren möchten, werden wir mit Behat eine Geschichte in unserer Funktionssuite schreiben.

3. Integration

Unsere Integrationssuite testet das Verhalten des Kernteils unserer Anwendung, für den kein Zugriff auf Laravel erforderlich ist. Die Integrationssuite besteht normalerweise aus einer Mischung aus Behat-Storys und PhpSpec-Spezifikationen.

4. Unit

Unsere Komponententests werden in PhpSpec geschrieben und isolierte kleine Einheiten des Anwendungskerns testen. Unsere Entitäten, Wertobjekte usw. haben alle Spezifikationen.

Der Fall

In dieser Reihe werden wir ab dem nächsten Artikel ein System zur Zeiterfassung entwickeln. Wir beginnen mit der Beschreibung des Verhaltens von außen, indem wir Behat-Funktionen schreiben. Das interne Verhalten unserer Anwendung wird mit PhpSpec beschrieben.

Zusammen helfen uns diese beiden Tools, uns mit der Qualität der von uns erstellten Anwendung wohl zu fühlen. Wir werden hauptsächlich Funktionen und Spezifikationen auf drei Ebenen schreiben:

  1. Funktionell
  2. Integration
  3. Unit


In unserer Funktionssuite werden die HTTP-Antworten unserer Anwendung in einem kopflosen Modus gecrawlt, was bedeutet, dass wir den Browser nicht durchlaufen. Dies erleichtert die Interaktion mit Laravel und macht unsere Funktionssuite auch zu unserer Akzeptanzschicht.

Wenn wir später eine kompliziertere Benutzeroberfläche haben und möglicherweise auch JavaScript testen müssen, fügen wir möglicherweise eine dedizierte Akzeptanzsuite hinzu. Diese Serie ist noch in Arbeit. Sie können Ihre Vorschläge also gerne im Kommentarbereich ablegen.

Unser Setup

Beachten Sie, dass für dieses Tutorial eine Neuinstallation von Laravel (4.2) ausgeführt wird. Am besten verwenden Sie auch Laravel Homestead, was ich beim Schreiben dieses Codes verwendet habe.

Bevor wir mit der eigentlichen Arbeit beginnen, stellen wir sicher, dass Behat und PhpSpec in Betrieb sind. Zuerst mache ich gerne ein bisschen Reinigung, wenn ich ein neues Laravel-Projekt starte und die Dinge lösche, die ich nicht brauche:

1
git rm -r app/tests/ phpunit.xml CONTRIBUTING.md

Wenn Sie diese Dateien löschen, müssen Sie Ihre Datei composer.json entsprechend aktualisieren:

1
"autoload": {
2
    "classmap": [
3
        "app/commands",
4
        "app/controllers",
5
        "app/models",
6
        "app/database/migrations",
7
        "app/database/seeds"
8
    ]
9
},

Und natürlich:

1
$ composer dump-autoload

Jetzt können wir die BDD-Tools einsetzen, die wir benötigen. Fügen Sie Ihrem composer.json einfach einen require-dev-Abschnitt hinzu:

1
"require": {
2
    "laravel/framework": "4.2.*"
3
},
4
"require-dev": {
5
    "behat/behat": "~3.0",
6
    "phpspec/phpspec": "~2.0",
7
    "phpunit/phpunit": "~4.1"
8
},

"Warum ziehen wir PHPUnit ein?" du denkst vielleicht? Wir werden in dieser Serie keine guten alten PHPUnit-Testfälle schreiben, aber die Behauptungen sind zusammen mit Behat ein nützliches Werkzeug. Wir werden das später in diesem Artikel sehen, wenn wir unser erstes Feature schreiben.

Denken Sie daran, Ihre Abhängigkeiten nach dem Ändern von composer.json zu aktualisieren:

1
$ composer update --dev

Wir sind fast fertig mit der Installation und Einrichtung. PhpSpec funktioniert sofort:

1
$ vendor/bin/phpspec run
2
3
0 specs
4
0 examples 
5
0ms

Behat muss jedoch einen schnellen Lauf mit der Option --init ausführen, um alles einzurichten:

1
$ vendor/bin/behat --init
2
3
+d features - place your *.feature files here
4
+d features/bootstrap - place your context classes here
5
+f features/bootstrap/FeatureContext.php - place your definitions, transformations and hooks here
6
7
$ vendor/bin/behat
8
9
No scenarios
10
No steps
11
0m0.14s (12.18Mb)

Mit dem ersten Befehl wurde eine glänzende neue FeatureContext-Klasse erstellt, in die wir die für unsere Features erforderlichen Schrittdefinitionen schreiben können:

1
<?php
2
3
use Behat\Behat\Context\SnippetAcceptingContext;
4
use Behat\Gherkin\Node\PyStringNode;
5
use Behat\Gherkin\Node\TableNode;
6
7
/**

8
 * Behat context class.

9
 */
10
class FeatureContext implements SnippetAcceptingContext
11
{
12
    /**

13
     * Initializes context.

14
     *

15
     * Every scenario gets its own context object.

16
     * You can also pass arbitrary arguments to the context constructor through behat.yml.

17
     */
18
    public function __construct()
19
    {
20
    }
21
}

Schreiben unseres ersten Features

Unsere erste Funktion wird sehr einfach sein: Wir werden einfach sicherstellen, dass unsere neue Laravel-Installation uns mit einem "Sie sind angekommen" begrüßt auf der Homepage. Ich habe auch einen ziemlich albernen Schritt Given, Given I am logged in, was nur zeigt, wie einfach die Interaktion mit Laravel in unseren Funktionen ist.

Technisch würde ich diese Art von Feature als Funktionstest kategorisieren, da es mit dem Framework interagiert, aber auch als Abnahmetest dient, da wir keine unterschiedlichen Ergebnisse sehen würden, wenn wir einen ähnlichen Test über ein Browsertest-Tool ausführen würden.  Im Moment bleiben wir bei unserer Funktionstestsuite.

Erstellen Sie eine Datei welcome.feature und fügen Sie sie in die features/functional ein:

1
# features/functional/welcome.feature
2
3
Feature: Welcoming developer
4
    As a Laravel developer
5
    In order to proberly begin a new project
6
    I need to be greeted upon arrival
7
8
    Scenario: Greeting developer on homepage
9
        Given I am logged in
10
        When I visit "/"
11
        Then I should see "You have arrived."

Indem wir die Funktionsmerkmale in ein functional verzeichnis stellen, können wir unsere Suiten später einfacher verwalten. Wir möchten keine Funktionen vom Typ Integration, bei denen Laravel nicht auf die langsame Funktionssuite warten muss.

Ich mag es, die Dinge schön und sauber zu halten, daher glaube ich, dass wir einen speziellen Funktionskontext für unsere Funktionssuite haben sollten, der uns den Zugriff auf Laravel ermöglicht. Sie können einfach die vorhandene FeatureContext-Datei kopieren und den Klassennamen in LaravelFeatureContext ändern. Damit dies funktioniert, benötigen wir auch eine Konfigurationsdatei behat.yml.

Erstellen Sie eine im Stammverzeichnis Ihres Projekts und fügen Sie Folgendes hinzu:

1
default:
2
    suites:
3
        functional:
4
            paths: [ %paths.base%/features/functional ]
5
            contexts: [ LaravelFeatureContext ]

Ich denke, die YAML hier ist ziemlich selbsterklärend. Unsere Funktionssuite sucht im functional verzeichnis nach Funktionen und führt sie über den LaravelFeatureContext aus.

Wenn wir versuchen, Behat an dieser Stelle auszuführen, werden wir aufgefordert, die erforderlichen Schrittdefinitionen zu implementieren. Behat kann die leeren Gerüstmethoden mit dem folgenden Befehl zum LaravelFeatureContext hinzufügen:

1
$ vendor/bin/behat --dry-run --append-snippets
2
$ vendor/bin/behat
3
4
Feature: Welcoming developer
5
    As a Laravel developer
6
    In order to proberly begin a new project
7
    I need to be greeted upon arival
8
9
  Scenario: Greeting developer on homepage # features/functional/welcome.feature:6

10
    Given I am logged in                   # LaravelFeatureContext::iAmLoggedIn()

11
      TODO: write pending definition
12
    When I visit "/"                       # LaravelFeatureContext::iVisit()

13
    Then I should see "You have arrived."  # LaravelFeatureContext::iShouldSee()

14
15
1 scenario (1 pending)
16
3 steps (1 pending, 2 skipped)
17
0m0.28s (12.53Mb)

Und jetzt, wie Sie der Ausgabe entnehmen können, können wir mit der Implementierung des ersten unserer Schritte beginnen: Given I am logged in.

Der mit Laravel gelieferte PHPUnit-Testfall ermöglicht es uns, Dinge wie $this->be($user) zu tun, die einen bestimmten Benutzer anmelden. Letztendlich möchten wir in der Lage sein, mit Laravel zu interagieren, als ob wir PHPUnit verwenden würden. Schreiben wir also den Schrittdefinitionscode "Wir wünschten, wir hätten":

1
/**

2
 * @Given I am logged in

3
 */
4
public function iAmLoggedIn()
5
{
6
    $user = new User;
7
8
    $this->be($user);
9
}

Dies wird natürlich nicht funktionieren, da Behat keine Ahnung von Laravel-spezifischen Dingen hat, aber ich werde Ihnen gleich zeigen, wie einfach es ist, Behat und Laravel dazu zu bringen, gut zusammen zu spielen.

Wenn Sie einen Blick in die Laravel-Quelle werfen und die Klasse Illuminate\Foundation\Testing\TestCase finden, von der der Standardtestfall ausgeht, werden Sie feststellen, dass ab Laravel 4.2 alles in ein Merkmal verschoben wurde. Das ApplicationTrait ist jetzt für das Booten einer Application instanz, das Einrichten eines HTTP-Clients und einige Hilfsmethoden wie be() verantwortlich.

Das ist ziemlich cool, vor allem, weil es bedeutet, dass wir es einfach in unsere Behat-Kontexte ziehen können, ohne dass ein Setup erforderlich ist. Wir haben auch Zugriff auf das AssertionsTrait, dies ist jedoch weiterhin an PHPUnit gebunden.

Wenn wir das Merkmal einbeziehen, müssen wir zwei Dinge tun. Wir benötigen eine setUp()-Methode wie die in der Klasse Illuminate\Foundation\Testing\TestCase und eine createApplication()-Methode wie die im Standard-Laravel-Testfall. Eigentlich können wir diese beiden Methoden einfach kopieren und direkt verwenden.

Es gibt nur eines zu beachten: In PHPUnit wird die Methode setUp() vor jedem Test automatisch aufgerufen. Um dasselbe in Behat zu erreichen, können wir die Annotation @BeforeScenario verwenden.

Fügen Sie Ihrem LaravelFeatureContext Folgendes hinzu:

1
use Illuminate\Foundation\Testing\ApplicationTrait;
2
3
/**

4
 * Behat context class.

5
 */
6
class LaravelFeatureContext implements SnippetAcceptingContext
7
{
8
    /**

9
     * Responsible for providing a Laravel app instance.

10
     */
11
    use ApplicationTrait;
12
13
    /**

14
     * Initializes context.

15
     *

16
     * Every scenario gets its own context object.

17
     * You can also pass arbitrary arguments to the context constructor through behat.yml.

18
     */
19
    public function __construct()
20
    {
21
    }
22
23
    /**

24
     * @BeforeScenario

25
     */
26
    public function setUp()
27
    {
28
        if ( ! $this->app)
29
        {
30
            $this->refreshApplication();
31
        }
32
    }
33
34
    /**

35
     * Creates the application.

36
     *

37
     * @return \Symfony\Component\HttpKernel\HttpKernelInterface

38
     */
39
    public function createApplication()
40
    {
41
        $unitTesting = true;
42
43
        $testEnvironment = 'testing';
44
45
        return require __DIR__.'/../../bootstrap/start.php';
46
    }

Ziemlich einfach und schauen Sie, was wir bekommen, wenn wir Behat ausführen:

1
$ vendor/bin/behat
2
3
Feature: Welcoming developer
4
    As a Laravel developer
5
    In order to proberly begin a new project
6
    I need to be greeted upon arival
7
8
  Scenario: Greeting developer on homepage # features/functional/welcome.feature:6

9
    Given I am logged in                   # LaravelFeatureContext::iAmLoggedIn()

10
    When I visit "/"                       # LaravelFeatureContext::iVisit()

11
      TODO: write pending definition
12
    Then I should see "You have arrived."  # LaravelFeatureContext::iShouldSee()

13
14
1 scenario (1 pending)
15
3 steps (1 passed, 1 pending, 1 skipped)
16
0m0.73s (17.92Mb)

Ein grüner erster Schritt, was bedeutet, dass unser Setup funktioniert!

Als nächstes können wir den Schritt When I visit implementieren. Dieser ist super einfach und wir können einfach die call()-Methode verwenden, die ApplicationTrait bereitstellt. Eine Codezeile bringt uns dorthin:

1
/**

2
 * @When I visit :uri

3
 */
4
public function iVisit($uri)
5
{
6
    $this->call('GET', $uri);
7
}

Der letzte Schritt, Then I should see, dauert etwas länger und wir müssen zwei Abhängigkeiten ziehen. Wir benötigen PHPUnit für die Bestätigung und den Symfony DomCrawler, um nach "You have arrived" zu suchen Text.

Wir können es so implementieren:

1
use PHPUnit_Framework_Assert as PHPUnit;
2
use Symfony\Component\DomCrawler\Crawler;
3
4
...
5
6
/**

7
 * @Then I should see :text

8
 */
9
public function iShouldSee($text)
10
{
11
    $crawler = new Crawler($this->client->getResponse()->getContent());
12
13
    PHPUnit::assertCount(1, $crawler->filterXpath("//text()[. = '{$text}']"));
14
}

Dies ist so ziemlich derselbe Code, den Sie schreiben würden, wenn Sie PHPUnit verwenden würden. Der Teil filterXpath() ist etwas verwirrend und wir werden uns jetzt keine Sorgen machen, da er etwas außerhalb des Geltungsbereichs dieses Artikels liegt. Vertrau mir einfach, dass es funktioniert.

Ein letztes Mal Behat zu laufen ist eine gute Nachricht:

1
$ vendor/bin/behat
2
Feature: Welcoming developer
3
    As a Laravel developer
4
    In order to proberly begin a new project
5
    I need to be greeted upon arival
6
7
  Scenario: Greeting developer on homepage # features/functional/welcome.feature:6

8
    Given I am logged in                   # LaravelFeatureContext::iAmLoggedIn()

9
    When I visit "/"                       # LaravelFeatureContext::iVisit()

10
    Then I should see "You have arrived."  # LaravelFeatureContext::iShouldSee()

11
12
1 scenario (1 passed)
13
3 steps (3 passed)
14
0m0.82s (19.46Mb)

Die Funktion funktioniert wie erwartet und der Entwickler wird bei der Ankunft begrüßt.

Abschluss

Der vollständige LaravelFeatureContext sollte nun ungefähr so aussehen:

1
<?php
2
3
use Behat\Behat\Context\SnippetAcceptingContext;
4
use Behat\Gherkin\Node\PyStringNode;
5
use Behat\Gherkin\Node\TableNode;
6
7
use PHPUnit_Framework_Assert as PHPUnit;
8
use Symfony\Component\DomCrawler\Crawler;
9
10
use Illuminate\Foundation\Testing\ApplicationTrait;
11
12
/**

13
 * Behat context class.

14
 */
15
class LaravelFeatureContext implements SnippetAcceptingContext
16
{
17
    /**

18
     * Responsible for providing a Laravel app instance.

19
     */
20
    use ApplicationTrait;
21
22
    /**

23
     * Initializes context.

24
     *

25
     * Every scenario gets its own context object.

26
     * You can also pass arbitrary arguments to the context constructor through behat.yml.

27
     */
28
    public function __construct()
29
    {
30
    }
31
32
    /**

33
     * @BeforeScenario

34
     */
35
    public function setUp()
36
    {
37
        if ( ! $this->app)
38
        {
39
            $this->refreshApplication();
40
        }
41
    }
42
43
    /**

44
     * Creates the application.

45
     *

46
     * @return \Symfony\Component\HttpKernel\HttpKernelInterface

47
     */
48
    public function createApplication()
49
    {
50
        $unitTesting = true;
51
52
        $testEnvironment = 'testing';
53
54
        return require __DIR__.'/../../bootstrap/start.php';
55
    }
56
57
    /**

58
     * @Given I am logged in

59
     */
60
    public function iAmLoggedIn()
61
    {
62
        $user = new User;
63
64
        $this->be($user);
65
    }
66
67
    /**

68
     * @When I visit :uri

69
     */
70
    public function iVisit($uri)
71
    {
72
        $this->call('GET', $uri);
73
    }
74
75
    /**

76
     * @Then I should see :text

77
     */
78
    public function iShouldSee($text)
79
    {
80
        $crawler = new Crawler($this->client->getResponse()->getContent());
81
82
        PHPUnit::assertCount(1, $crawler->filterXpath("//text()[. = '{$text}']"));
83
    }
84
}

Wir haben jetzt eine wirklich gute Grundlage, auf der wir aufbauen können, wenn wir unsere neue Laravel-Anwendung mit BDD weiterentwickeln. Ich hoffe, ich habe Ihnen bewiesen, wie einfach es ist, Laravel und Behat dazu zu bringen, gut zusammen zu spielen.

Wir haben in diesem ersten Artikel viele verschiedene Themen angesprochen. Kein Grund zur Sorge, wir werden uns im weiteren Verlauf der Serie eingehender mit allem befassen. Wenn Sie Fragen oder Anregungen haben, hinterlassen Sie bitte einen Kommentar.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
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.