Advertisement
  1. Code
  2. Laravel

Laravel, le BDD et Vous: Commençons

Scroll to top
Read Time: 14 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)

Bienvenue dans cette série d'article consacré au développement d'application avec Laravel en utilisant le behaviour-driven development (BDD). Le procédé BDD complet peut sembler compliqué et intimidant, mais il y a autant de façon de le faire qu'il y a de développeurs.

Dans cette série, je vous ferez part de mon approche en utilisant Behat et PhpSpec pour la réalisation d'une nouvelle application Laravel.

Il existe de nombreuses informations générales sur le BDD, mais les informations spécifiques à Laravel sont difficiles à trouver. De ce fait, dans cette série, nous nous concentrerons plus sur les aspects spécifiques à Laravel et moins sur les généralités sur lesquelles vous pouvez trouver de la lecture aisément.

Décrire un comportement

Lorsque nous décrivons un comportement, ce qui est aussi connu comme l'écriture de scénarios (stories) ou de spécifications, nous allons utiliser une approche de l'extérieur vers l'intérieur. Cela veut dire que chaque fois que nous allons construire une nouvelle fonctionnalité, nous commencerons par écrire le scénario utilisateur complet. Il s'agit donc de la vue du client.

Que souhaitons nous qu'il se passe lorsque nous faisons ceci?

Nous ne sommes pas autorisé à écrire une seule ligne de code tant que nous n'avons pas un test qui échoue, sauf si nous réécrivons du code existant.

Parfois, il sera nécessaire de résoudre itérativement une petite partie d'un scénario ou fonctionnalité (deux mots que j'utilise de façon interchangeable) sur lequel nous travaillons. Cela voudra souvent dire, écrire une spécification avec PhpSpec. Parfois cela prendra de nombreux essais sur un test unitaire ou d'intégration avant qu'un scénario complet (au niveau de l'acceptance) ne passe les tests. Tout cela semble compliqué mais ne l'est pas. Je suis un grand croyant de l'apprentissage par la pratique, donc je pense que tout cela prendra plus de sens lorsque nous commencerons à écrire du code.

Nous allons écrire des scénarios et des spécifications à quatre niveaux différents:

1. Acceptance

La plupart du temps, nos scénarios fonctionnels serviront à notre couche d'acceptance. La façon dont nous allons décrire nos fonctionnalités dans nos scénarios fonctionnels sera très proche de la façon dont nous écrierions nos critères d'acceptance (en utilisant un framework automatique dans un navigateur) et cela créerait beaucoup de répétition.

Tant que les scénarios décrivent le comportement depuis la vue client, ils peuvent servir de tests d'acceptance. Nous allons utiliser le composant Symfony DomCrawler pour tester la sortie de notre application. Plus tard dans cette série, nous verrons peut-être qu'il sera également nécessaire de faire passer les tests dans un véritable navigateur capable d'exécuter du JavaScript. Tester avec un navigateur ajoute de nouvelles préoccupations puisque nous devons nous assure que nous chargeons bien notre environnement de test.

2. Fonctionnel

Pour nos scénarios fonctionnels, nous aurons accès à l'application Laravel, ce qui est pratique. En premier lieu, cela rend la séparation des environnements facile. Deuxièmement, ne pas devoir utiliser un navigateur rend nos tests bien plus rapide. A chaque développement d'une nouvelle fonctionnalité, nous écrirons nos scénarios fonctionnels en utilisant Behat.

3. Intégration

Nos scénarios d'intégration vérifierons le comportement de partie essentielle de notre application pour lesquelles nous n'avons pas spécialement besoin d'accéder à Laravel. Les tests d'intégration seront un mélange de scenarios Behat et de spécifications PhpSpec.

4. Unitaire

Nos tests unitaires seront écrit avec PhpSpec et testerons de petites parties du coeur de l'application. Nos entités, objets de valeurs, etc… auront leurs spécifications.

L'application

Au cours de cette série, à partir du prochain article, nous allons construire un système de capture du temps de travail. Nous commencerons par décrire le comportement depuis l'extérieur en utilisant Behat. Le comportement interne de notre application sera décrit en utilisant PhpSpec.

Ensemble, ces deux outils, nous aiderons à nous sentir à l'aise par rapport à la qualité de l'application que nous développerons. Nous écrirons principalement nos spécifications sur trois niveaux:

  1. Fonctionnel
  2. Intégration
  3. Unitaire

Dans nos scénarios fonctionnels, nous analyserons les réponses HTTP de notre application de façon facile, sans navigateur. Cela rendra les interactions avec Laravel facile et fera de nos scénarios fonctionnels également une couche d'acceptance.

Plus tard, si nous avons une interface utilisateur plus complexe et que nous devons également tester du JavaScript, nous pourrions ajouter des scénarios d'acceptance dédiés. Cette série est encore en cours d'élaboration, donc n'hésitez pas à faire part de vos suggestions dans les commentaires.

Notre configuration

Pour ce tutoriel, je considère que vous avez une nouvelle installation de Laravel (4.2) fonctionnelle. De préférence, vous utilisez également Laravel Homestead, que j'ai utilisé pour écrire ce code.

Avant de commencer avec le vrai travail, assurons nous d'avoir Behat et PhpSpec installer et prêt à fonctionner. Tout d'abord, j'aime faire un peu de nettoyage à chaque fois que je démarre un nouveau projet Laravel et supprimer ce dont je n'ai pas besoin:

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

Si vous supprimer ces fichiers, assurez vous de mettre à jour le fichier composer.json en conséquence:

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

et bien entendu:

1
$ composer dump-autoload

Maintenant, nous sommes prêts à ajouter les outils BDD dont nous avons besoin. Ajoutez une section require-dev à votre compser-json:

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
},

"Pourquoi installer PHPUnit?" pourriez-vous vous demander? Nous n'allons pas écrire de tests PHPUnit dans cette série, mais les assertions sont des outils pratiques en combinaisons avec Behat. Nous verrons ça plus loin dans cet article lorsque nous écrirons notre premier scénario.

Pensez à mettre à jour vos dépendances après avoir modifier composer.json:

1
$ composer update --dev

Nous avons presque fini avec l'installation et la configuration. PhpSpec fonctionne directement:

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

Mais Behat a besoin d'être appeler une première fois avec l'option --init pour mettre tout en place:

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)

La première commande crée une nouvelle classe FeatureContext où nous pouvons écrire les étapes nécessaire à notre scénario:

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
}

Ecrire notre premier scénario

Notre premier scénario sera très simple: nous allons simplement nous assurer que notre nouvelle installation de Laravel nous annonce "You have arrived" sur la homepage. J'ajoute une étape Given un peu simpliste, Given I am logged in, qui sert uniquement à démontrer comme il est facile d'interagir avec Laravel depuis nos scénarios.

Techniquement, je catégorise ce type de scénario comme fonctionnel, puisqu'il interagit avec le framework, mais il sert aussi de scénario d'acceptance puisque nous ne verrions aucune différence en faisant tourner le test dans un navigateur. Pour le moment nous nous en tiendrons à un scénario fonctionnel.

Allons-y et créons un fichier welcome.feature dans le dossier features/functional:

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."

En plaçant les scénarios fonctionnels dans un répertoire functional, il sera plus facile de gérer l'ensemble de nos scénarios plus tard. Nous ne voulons pas que des scénarios d'intégration, qui n'ont pas besoin de Laravel, aient besoin d'attendre l'exécution, plus lente, de l'ensemble des scénarios fonctionnels.

J'aime garder les choses simples et claires, donc je pense que nous devrions avoir un contexte dédié, pour notre ensemble fonctionnel, qui nous donne accès à Laravel. Nous allons donc juste copier le fichier FeatureContext existant et changer le nom de la classe en LaravelFeatureContext. Pour que cela fonctionne, nous avons aussi besoin d'un fichier de configuration behat.yml

Créer s'en un à la racine de votre projet et ajouter ce qui suit:

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

Je pense que le fichier YAML est assez explicite. Notre ensemble fonctionnel va chercher les scénarios dans le répertoire functional et les exécuter par l'intermédiarie de LaravelFeatureContext.

Si nous essayons d'exécuter Behat à ce point, il va nous dire qu'il est nécessaire d'implémenter les définitions des étapes. Behat peut se charger de créer les méthodes vide dans LaravelFeatureContext grâce à la commande suivante:

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)

Maintenant, comme le montre la sortie de la commande, nous sommes prêt à démarrer l'implémentation de notre première étape: Given I am logged in.

Les tests case PHPUnit qui sont livrés avec Laravel nous autorise à faire des choses comme $this->be($user), ce qui log un utilisateur donné. En définitive, nous souhaitons avoir la possibilité d'interagir avec Laravel comme si nous utilisions PHPUnit, donc écrivons le code "comme nous souhaiterions l'avoir":

1
/**

2
 * @Given I am logged in

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

Ceci ne fonctionnera évidement pas, Behat ignorant les spécificités de Laravel, mais je vais vous montrer dans quelques secondes comme il est facile de faire travailler Behat et Laravel ensemble.

Si vous regardez le code source de Laravel et trouvez la classe Illuminate/Foundation/Testing/TestCase que la classe de test par défaut étend, vous verrez que depuis Laravel 4.2, tout à été déplacé dans un Trait. C'est ApplicationTrait qui est maintenant responsable de démarrer une instance d'une Application, de mettre en place le client HTTP et nous fournir quelques méthodes d'aide, comme be().

C'est plutôt cool, principalement car cela veut dire que l'on peut juste l'utiliser dans notre contexte Behat pratiquement sans configuration. Nous avons également accès au AssertionsTrait, mais ceci est toujours lié à PHPUnit.

Quand nous ajoutons le Trait, nous devons faire deux choses. Nous avons besoins d'une méthode setUp(), comme celle que l'on retrouve dans la classe Illuminate/Foundation/Testing/TestCase, et nous avons besoin d'une méthode createApplication() comme celle que l'on retrouve dans le cas de test par défaut de Laravel. En fait, nous pouvons juste copier ces deux méthodes et les utiliser directement.

Il faut juste noter une chose: dans PHPUnit, la méthode setUp() est appelée automatiquement avant chaque test. Pour obtenir le même comportement avec Behat, nous devons utiliser l'annotation @BeforeScenario.

Ajouter le code suivant à LaravelFeatureContext:

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
    }

Plutôt facile, et regardez ce que l'on obtient en exécutant Behat:

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)

Une première étape verte, ce qui signifie que notre environnement fonctionne!

Ensuite, nous pouvons implémenter l'étape When I visit. Celle-ci est très facile, nous pouvons simplement appeler la méthode call() que ApplicationTrait fournit. Une seule ligne de code suffit donc:

1
/**

2
 * @When I visit :uri

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

La dernière étape, Then I should see, demande un peu plus de travail et nous devons ajouter deux dépendances. Nous allons avoir besoin de PHPUnit pour les assertions et Symfony DowCrawler pour chercher le texte "You have arrived.".

Nous pouvons l'implémenter de cette façon:

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
}

Nous écriverions à peu près le même code si nous utilisions PHPUnit. La partie filterXPath est un peu confuse mais nous n'allons pas trop nous en préoccuper, c'est un peu en dehors du sujet de cet article. Faites moi juste confiance, ça fonctionne.

Faire tourner Behat une dernière fois nous donne de bonnes nouvelles:

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)

Le scénario fonctionne comme voulu et le développeur est accueillis comme il se doit.

Conclusion

Le fichier complet LaravelFeatureContext doit maintenant contenir ceci:

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
}

Nous avons maintenant une bonne base pour construire la suite de notre nouvelle application Laravel en utilisant BDD. J'espère vous avoir démontrer comme il est aisé de faire fonctionner Laravel et Behat ensemble.

Nous avons toucher à de nombreux concepts dans ce premier article. Pas besoin de s'en faire, nous étudierons plus en profondeur chaque aspect dans la suite de cette série d'article. Si vous avez la moindre question ou suggestion, n'hésitez pas à laisser un commentaire.

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.