Permalink
Switch branches/tags
Nothing to show
Find file Copy path
ec83572 Jun 20, 2012
1 contributor

Users who have contributed to this file

586 lines (421 sloc) 19.5 KB

Rédiger des spécifications - La syntaxe Gherkin

Behat est un outil pour décrire le comportement de votre application. Ce comportement est décrit dans une langue particulière, appelée Gherkin.

Gherkin est un langage "Spécifique à un Domaine fonctionnel, et lisible par un Fonctionnel" <http://martinfowler.com/bliki/BusinessReadableDSL.html>`_, et a été imaginé spécialement pour la description de comportements. Il vous offre la possibilité d'abstraire les détails d'implémentation de vos tests de comportement.

Gherkin a deux objectifs : servir de documentation fonctionnelle pour votre projet, et servir de tests automatisés. Behat offre même un bonus : il guide le développeur en utilisant un vrai langage, humain, et lui dit ce que son code doit produire.

Si vous découvrez Behat, allez faire un tour sur le :doc:`/quick_intro` d'abord, puis revenez ici pour en découvrir plus sur Gherkin.

La Syntaxe Gherkin

Comme c'est le cas pour YAML ou Python, Gherkin est une language "orienté ligne", c'est-à-dire qu'il utilise les indentations pour définir sa structure. Les fins de lignes terminent des instructions (appelées "étapes") ; les espaces, comme les tabulations, peuvent être utilisée pour l'indentation. (Nous suggérons d'utiliser les espaces, pour des raisons de portabilité). Pour finir, la plupart des lignes écrite en Gherkin comment par un mot-clef spécial :

Fonctionnalité: Une description de ce qui est attendu
  Afin de réaliser une action métier (domaine fonctionnel)
  En tant qu'acteur explicite du domaine fonctionnel
  Je dois tirer un bénéfice de cette fonctionnalité

  Scénario: Une description du scénario
      Etant donné que [un contexte]
      Et [plus d'informations sur le contexte]
      Quand [un événement]
      Et [un autre événement]
      Alors [résultat attendu]
      Et [un autre résultat attendu]
      Mais [un autre résultat attendu]

  Scénario: Une situation différente
      ...

L'interpréteur découpe les informations en Fonctionnalités, Scénarios puis Étapes. Examinons plus en détail l'exemple ci dessus :

  1. Fonctionnalité: Une description de ce qui est attendu démarre la fonctionnalité et lui donne un titre. Découvrez en plus sur les Fonctionnalités dans la section "Fonctionnalités".
  2. Behat ne va pas interpréter les 3 lignes suivantes (Afin de... En tant que... Je dois...). Ces lignes ne servent qu'à fournir un contexte fonctionnel/métier lié à votre fonctionnalité, dans votre application.
  3. Scénario: Une description du scénario démarre le scénario, et contient une description du scénario. Découvrez en plus sur les Fonctionnalités dans la section "Scénarios".
  4. Les 7 lignes suivantes sont les Étapes du Scénario, chacune matchant une expression régulière définie ailleurs. Découvrez en plus sur les Étapes dans la section "`Étapes`_".
  5. Scénario: Une situation différente démarre le scénario suivant, puis ça continue....

Quand vous exécutez la fonctionnalité, l'expression contenue dans chaque Étape (après les mot-clefs comment Etant donné, Et, Quand, etc.) est liée à une expression régulière qui va la lier à une fonction PHP. Vous pouvez en savoir plus sur le matching d'expression et leur exécution dans :doc:`/guides/2.definitions`.

Fonctionnalités

Chaque fichier *.feature ne doit contenir qu'une seule fonctionnalité. Le fichier doit commencer par le mot-clef Fonctionnalité (ou sa traduction), suivi par trois lignes indentées. Une fonctionnalité contient généralement une liste de Scénarios. Vous pouvez écrire ce que vous voulez au dessus du premier scénario (ce ne sera pas interpreté), qui démarrera avec le mot-clef Scénario (ou sa traduction). Vous pouvez enfin utiliser des tags pour regrouper les fonctionnalités et les scénarios, indépendamment du fichier ou du dossier dans lesquels ils sont situés.

Note

Comme le Français n'est pas la langue par défaut de Behat, n'oubliez pas d'ajouter le commentaire # language: fr en haut de votre Fonctionnalité.

Chaque Scénario contient une liste d'Étapes, qui doivent débuter par le mot-clef Etant donné, Quand, Alors, Et ou Mais. Behat ne fait aucune différence entre ces mot-clefs, mais ils sont important pour les humains. Voici un exemple :

Fonctionnalité: Servir du café
  Afin de gagner de l'argent
  Les clients doivent être capables
  d'acheter du café à toutes heures

  Scénario: Acheter le dernier café
    Etant donné qu'il reste 1 café dans la machine
    Et que j'ai mis 1 dollar
    Quand j'appuie sur le bouton de la machine
    Alors je devrai recevoir un café
En plus des scénarios de base, une fonctionnalité peut contenir des
Plans de scénario et des Contextes.

Scénarios

Le Scénario est une des structures à la base de Gherkin. Chaque schénario démarre avec le mot-clef Scénario (ou sa traduction), suivi par un titre de scénario optionnel. Chaque fonctionnalité peut être composée de un ou de plusieurs scénarios, et chaque scénario consiste en une ou plusieurs `étapes`_.

Les scénarios suivants sont composés de 3 étapes :

Scenario: Wilson publie sur son propre blog
  Etant donné que je suis connecté en tant que Wilson
  Quand je tente de publier pour "Le Blog de Wilson"
  Alors je dois voir "Votre article a été publié"

Scenario: Wilson échoue lorsqu'il tente de publier sur un autre blog
  Etant donné que je suis connecté en tant que Wilson
  Quand je tente de publier pour "Le Blog de Greg"
  Alors je dois voir "Hé! Ce n'est pas votre blog !"

Scenario: Greg publie sur son propre blog
  Etant donné que je suis connecté en tant que Greg
  Quand je tente de publier pour "Le Blog de Greg"
  Alors je dois voir "Votre article a été publié"

Plans de scénario

Copier-coller des scénarios pour utiliser différentes valeurs peut être vite fastidieux et répétitif :

Scenario: En manger 5 sur 12
  Etant donné qu'il y a 12 concombres
  Quand je mange 5 concombres
  Alors je dois avoir 7 concombres

Scenario: En manger 5 sur 20
  Etant donné qu'il y a 20 concombres
  Quand je mange 5 concombres
  Alors je dois avoir 15 concombres

Les Plans de scénarios vous permettent d'exprimer ces exemples en utilisant un tableau juste derrière votre scénario :

Plan de scénario: Manger
  Etant donné qu'il y a <initial> concombres
  Quand je mange <mangés> concombres
  Alors je dois avoir <restant> concombres

  Exemples:
    | initial | mangés | restant |
    |  12     |  5     |  7   |
    |  20     |  5     |  15  |

Les Plans de scénario fournissent un modèle qui n'est jamais exécuté d'un coup. Un Plan de scénario est en réalité exécuté pour chacune des lignes qui composent la section d'Exemples qui le suit (en excluant bien entendu la première ligne d'en-tête).

Le Plan de scénario utilise des marqueurs, qui sont contenus à l'intérieur des < > dans les étapes. Par exemple :

Etant donné <Je suis un marqueur valide>

Représentez-vous un marqueur comme une variable. Il sera remplacé par une valeur bien réelle issue du tableau d'Exemples:. La valeur substituée au marqueur change à chaque boucle dans l'exécution du Scénario attendu, jusqu'à ce que la fin du tableau d'Exemples soit atteinte.

Tip

Vous pouvez également utiliser des marqueurs dans des `Arguments multi-lignes`_.

Note

Vos définitions d'étapes ne doivent jamais matcher le contenu du marqueyr lui-même, mais doivent s'occuper uniquement de la valeur qui remplacera le marqueur.

Lorsqu'on exécute la première ligne de notre exemple :

Plan de scénario: Manger
  Etant donné qu'il y a <initial> concombres
  Quand je mange <mangés> concombres
  Alors je dois avoir <restant> concombres

  Exemples:
    | initial | mangés | restant |
    |  12     |  5     |  7   |
    |  20     |  5     |  15  |

Le scénario qui est en train de s'exécuter alors est :

Plan de scénario: Manger
  # <initial> est remplacé par 12 :
  Etant donné qu'il y a <initial> concombres
  # <mangés> est remplacé par 5 :
  Quand je mange <mangés> concombres
  # <restant> est remplacé par 7 :
  Alors je dois avoir <restant> concombres

Contextes

Les Contextes vous permettent d'ajouter une toile de fond commune à l'ensemble des scénarios d'une fonctionnalité en une seule fois. Un Contexte est comme un Scénario sans titre, et contient plusieurs étapes. La différence fondamentale c'est que le Contexte sera appelé avant chacun de vos scénarios (mais juste après vos Hoosk BeforeScenario (:doc:`/guides/3.hooks`)).

Feature: Multiple site support

  Background:
    Given a global administrator named "Greg"
    And a blog named "Greg's anti-tax rants"
    And a customer named "Wilson"
    And a blog named "Expensive Therapy" owned by "Wilson"

  Scenario: Wilson posts to his own blog
    Given I am logged in as Wilson
    When I try to post to "Expensive Therapy"
    Then I should see "Your article was published."

  Scenario: Greg posts to a client's blog
    Given I am logged in as Greg
    When I try to post to "Expensive Therapy"
    Then I should see "Your article was published."

Steps

Les Fonctionnalités sont composées d'étapes, c'est-à-dire `Etant donné`_, `Quand`_ and `Alors`_.

Behat ne distingue pas techniquement ces trois types d'étapes. Cependant, nous vous recommandons très fortement de le faire vous ! Ces mot-clefs ont été choisis avec soin parce qu'ils répondent à leur objectif, objectif que vous devez garder en tête pour faire du Développement Piloté par le Comportement.

Robert C. Martin a écrit un bon billet sur les concepts "Etant donné - Quand - Alors" du BDD, dans lequel il les pense comme un Automate fini (également désigné en Anglais par FSM, ou Finite State Machine).

Givens

The purpose of Given steps is to put the system in a known state before the user (or external system) starts interacting with the system (in the When steps). Avoid talking about user interaction in givens. If you have worked with use cases, givens are your preconditions.

Note

Two good examples of using Givens are:

  • To create records (model instances) or set up the database:

    Given there are no users on site
    Given the database is clean
  • Authenticate a user (An exception to the no-interaction recommendation. Things that "happened earlier" are ok):

    Given I am logged in as "Everzet"

Tip

It's ok to call into the layer "inside" the UI layer here (in symfony: talk to the models).

And for all the symfony users out there, we recommend using a Given step with a tables arguments to set up records instead of fixtures. This way you can read the scenario all in one place and make sense out of it without having to jump between files:

Given there are users:
  | username | password | email               |
  | everzet  | 123456   | everzet@knplabs.com |
  | fabpot   | 22@222   | fabpot@symfony.com  |

Whens

The purpose of When steps is to describe the key action the user performs (or, using Robert C. Martin's metaphor, the state transition).

Note

Two good examples of Whens use are:

  • Interact with a web page (the Mink library gives you many web-friendly When steps out of the box):

    When I am on "/some/page"
    When I fill "username" with "everzet"
    When I fill "password" with "123456"
    When I press "login"
  • Interact with some CLI library (call commands and record output):

    When I call "ls -la"

Thens

The purpose of Then steps is to observe outcomes. The observations should be related to the business value/benefit in your feature description. The observations should inspect the output of the system (a report, user interface, message, command output) and not something deeply buried inside it (that has no business value and is instead part of the implementation).

  • Verify that something related to the Given+When is (or is not) in the output
  • Check that some external system has received the expected message (was an email with specific content successfully sent?)
When I call "echo hello"
Then the output should be "hello"

Note

While it might be tempting to implement Then steps to just look in the database – resist the temptation. You should only verify output that is observable by the user (or external system). Database data itself is only visible internally to your application, but is then finally exposed by the output of your system in a web browser, on the command-line or an email message.

And, But

If you have several Given, When or Then steps you can write:

Scenario: Multiple Givens
  Given one thing
  Given an other thing
  Given yet an other thing
  When I open my eyes
  Then I see something
  Then I don't see something else

Or you can use And or But steps, allowing your Scenario to read more fluently:

Scenario: Multiple Givens
  Given one thing
  And an other thing
  And yet an other thing
  When I open my eyes
  Then I see something
  But I don't see something else

If you prefer, you can indent scenario steps in a more programmatic way, much in the same way your actual code is indented to provide visual context:

Scenario: Multiple Givens
  Given one thing
    And an other thing
    And yet an other thing
  When I open my eyes
  Then I see something
    But I don't see something else

Behat interprets steps beginning with And or But exactly the same as all other steps. It doesn't differ between them - you should!

Multiline Arguments

The regular expression matching in steps lets you capture small strings from your steps and receive them in your step definitions. However, there are times when you want to pass a richer data structure from a step to a step definition.

This is what multiline step arguments are for. They are written on lines immediately following a step, and are passed to the step definition method as the last argument.

Multiline step arguments come in two flavours: tables or pystrings.

Tables

Tables as arguments to steps are handy for specifying a larger data set - usually as input to a Given or as expected output from a Then.

Scenario:
  Given the following people exist:
    | name  | email           | phone |
    | Aslak | aslak@email.com | 123   |
    | Joe   | joe@email.com   | 234   |
    | Bryan | bryan@email.org | 456   |

Note

Don't be confused with tables from `scenario outlines`_ - syntactically they are identical, but have a different purpose.

Tip

A matching definition for this step looks like this:

/**
 * @Given /the following people exist:/
 */
public function thePeopleExist(TableNode $table)
{
    $hash = $table->getHash();
    foreach ($hash as $row) {
        // $row['name'], $row['email'], $row['phone']
    }
}

Note

A table is injected into a definition as a TableNode object, from which you can get hash by columns (TableNode::getHash() method) or by rows (TableNode::getRowsHash()).

PyStrings

Multiline Strings (also known as PyStrings) are handy for specifying a larger piece of text. This is done using the so-called PyString syntax. The text should be offset by delimiters consisting of three double-quote marks (""") on lines by themselves:

Scenario:
  Given a blog post named "Random" with:
    """
    Some Title, Eh?
    ===============
    Here is the first paragraph of my blog post.
    Lorem ipsum dolor sit amet, consectetur adipiscing
    elit.
    """

Note

The inspiration for PyString comes from Python where """ is used to delineate docstrings, much in the way /* ... */ is used for multiline docblocks in PHP.

Tip

In your step definition, there's no need to find this text and match it in your regular expression. The text will automatically be passed as the last argument into the step definition method. For example:

/**
 * @Given /a blog post named "([^"]+)" with:/
 */
public function blogPost($title, PyStringNode $markdown)
{
    $this->createPost($title, $markdown->getRaw());
}

Note

PyStrings are stored in a PyStringNode instance, which you can simply convert to a string with (string) $pystring or $pystring->getRaw() as in the example above.

Note

Indentation of the opening """ is not important, although common practice is two spaces in from the enclosing step. The indentation inside the triple quotes, however, is significant. Each line of the string passed to the step definition's callback will be de-indented according to the opening """. Indentation beyond the column of the opening """ will therefore be preserved.

Tags

Tags are a great way to organize your features and scenarios. Consider this example:

@billing
Feature: Verify billing

  @important
  Scenario: Missing product description

  Scenario: Several products

A Scenario or Feature can have as many tags as you like, just separate them with spaces:

@billing @bicker @annoy
Feature: Verify billing

Note

If a tag exists on a Feature, Behat will assign that tag to all child Scenarios and Scenario Outlines too.

Gherkin in Many Languages

Gherkin is available in many languages, allowing you to write stories using localized keywords from your language. In other words, if you speak French, you can use the word Fonctionnalité instead of Feature.

To check if Behat and Gherkin support your language (for example, French), run:

behat --story-syntax --lang=fr

Note

Keep in mind that any language different from en should be explicitly marked with a # language: ... comment at the beginning of your *.feature file:

# language: fr
Fonctionnalité: ...
  ...

This way your features will hold all the information about its content type, which is very important for methodologies like BDD, and will also give Behat the ability to have multilanguage features in one suite.