Gherkin - Langage de Spécification BDD

Introduction

Gherkin est un langage de spécification lisible par les humains qui permet de décrire le comportement d'une application de manière structurée. Il est principalement utilisé dans le cadre du Behavior-Driven Development (BDD) avec des outils comme Cucumber, SpecFlow, ou Behave.

Principes du BDD

@startuml
package "BDD - Behavior Driven Development" {
  [Business] as business
  [Développeurs] as dev
  [Testeurs] as qa
  [Gherkin] as gherkin
}

business --> gherkin : Écrit les scénarios
dev --> gherkin : Implémente les steps
qa --> gherkin : Valide les tests

note right of gherkin
  Langage commun compréhensible
  par tous les acteurs du projet
end note
@enduml

Avantages de Gherkin

  • Lisible : Compréhensible par les non-techniques (métier, PO, QA)
  • Structuré : Format standardisé et prévisible
  • Exécutable : Les scénarios deviennent des tests automatisés
  • Documentation vivante : Les tests documentent le comportement attendu
  • Collaboration : Langage commun entre métier et technique

Syntaxe de Base

Structure d'un Fichier Gherkin

# Commentaire
Feature: Titre de la fonctionnalité
  Description de la fonctionnalité
  sur plusieurs lignes si nécessaire

  Background:
    Given contexte commun à tous les scénarios

  Scenario: Titre du scénario
    Given contexte initial
    When action effectuée
    Then résultat attendu

  Scenario Outline: Scénario avec exemples
    Given un contexte avec <paramètre>
    When une action avec <valeur>
    Then un résultat <attendu>

    Examples:
      | paramètre | valeur | attendu |
      | param1    | val1   | result1 |
      | param2    | val2   | result2 |

Mots-Clés Principaux

Mot-clé Signification Usage
Feature Fonctionnalité Décrit la fonctionnalité testée
Scenario Scénario Un cas de test spécifique
Given Étant donné Contexte initial / préconditions
When Quand Action déclenchée
Then Alors Résultat attendu / assertions
And Et Continue l'étape précédente
But Mais Négation ou exception
Background Contexte Étapes communes à tous les scénarios
Scenario Outline Plan de scénario Scénario paramétré
Examples Exemples Données pour le Scenario Outline

Feature (Fonctionnalité)

Syntaxe

Feature: Authentification utilisateur
  En tant qu'utilisateur
  Je veux pouvoir me connecter à l'application
  Afin d'accéder à mes données personnelles

Structure Recommandée

Feature: [Nom de la fonctionnalité]
  En tant que [rôle]
  Je veux [action]
  Afin de [bénéfice]

Exemple Complet

Feature: Gestion du panier d'achat
  En tant que client
  Je veux pouvoir ajouter et retirer des articles de mon panier
  Afin de préparer ma commande avant de payer

  Le panier doit permettre :
  - L'ajout d'articles
  - La modification des quantités
  - La suppression d'articles
  - Le calcul automatique du total

Scenario (Scénario)

Scénario Simple

Scenario: Connexion réussie avec des identifiants valides
  Given l'utilisateur est sur la page de connexion
  And l'utilisateur a un compte avec l'email "john@example.com"
  When l'utilisateur saisit "john@example.com" dans le champ email
  And l'utilisateur saisit "Password123!" dans le champ mot de passe
  And l'utilisateur clique sur le bouton "Se connecter"
  Then l'utilisateur est redirigé vers la page d'accueil
  And un message "Bienvenue John" est affiché

Diagramme de Flux

@startuml
start
:Given: Page de connexion;
:Given: Compte existant;
:When: Saisie email;
:When: Saisie mot de passe;
:When: Clic sur "Se connecter";
:Then: Redirection accueil;
:Then: Message de bienvenue;
stop
@enduml

Given, When, Then

Given (Étant donné) - Contexte

Définit le contexte initial et les préconditions.

Given l'utilisateur est connecté
Given le panier contient 3 articles
Given la base de données contient les utilisateurs suivants:
  | nom    | email              | rôle  |
  | Alice  | alice@example.com  | admin |
  | Bob    | bob@example.com    | user  |

When (Quand) - Action

Décrit l'action ou l'événement qui se produit.

When l'utilisateur clique sur "Ajouter au panier"
When l'utilisateur soumet le formulaire
When le système reçoit une notification

Then (Alors) - Résultat

Spécifie le résultat attendu ou les assertions.

Then le panier contient 4 articles
Then un message de confirmation est affiché
Then l'utilisateur reçoit un email de confirmation
Then la réponse HTTP a le statut 200

And / But - Continuité

Scenario: Ajout d'un article au panier
  Given l'utilisateur est sur la page produit
  And le produit est en stock
  When l'utilisateur clique sur "Ajouter au panier"
  Then le panier contient 1 article
  And le prix total est de 29.99
  But le stock du produit est décrémenté de 1

Background (Contexte Commun)

Le Background permet de définir des étapes communes à tous les scénarios d'une Feature.

Feature: Gestion des commandes

  Background:
    Given l'utilisateur "john@example.com" est connecté
    And l'utilisateur a l'adresse de livraison suivante:
      | rue           | 123 Rue de la Paix |
      | ville         | Paris              |
      | code postal   | 75001              |

  Scenario: Passer une commande avec un article
    Given le panier contient 1 article
    When l'utilisateur valide sa commande
    Then la commande est créée avec succès

  Scenario: Passer une commande avec plusieurs articles
    Given le panier contient 3 articles
    When l'utilisateur valide sa commande
    Then la commande est créée avec succès
@startuml
start
partition Background {
  :Connexion utilisateur;
  :Adresse de livraison;
}
partition "Scenario 1" {
  :1 article dans panier;
  :Validation commande;
  :Commande créée;
}
partition "Scenario 2" {
  :3 articles dans panier;
  :Validation commande;
  :Commande créée;
}
stop
@enduml

Scenario Outline (Scénario Paramétré)

Permet d'exécuter le même scénario avec différents jeux de données.

Feature: Calculatrice

  Scenario Outline: Addition de deux nombres
    Given j'ai saisi <nombre1> dans la calculatrice
    When j'additionne <nombre2>
    Then le résultat devrait être <résultat>

    Examples:
      | nombre1 | nombre2 | résultat |
      | 2       | 3       | 5        |
      | 10      | 15      | 25       |
      | -5      | 5       | 0        |
      | 100     | 200     | 300      |

Plusieurs Tables d'Exemples

Scenario Outline: Connexion avec différents types d'utilisateurs
  Given l'utilisateur "<email>" avec le mot de passe "<password>"
  When l'utilisateur se connecte
  Then l'utilisateur a le rôle "<rôle>"
  And l'utilisateur voit la page "<page>"

  Examples: Utilisateurs valides
    | email              | password    | rôle  | page     |
    | admin@example.com  | Admin123!   | admin | dashboard|
    | user@example.com   | User123!    | user  | home     |

  Examples: Utilisateurs invalides
    | email              | password    | rôle  | page     |
    | bad@example.com    | Wrong!      | none  | login    |

Data Tables (Tables de Données)

Table Simple

Given les utilisateurs suivants existent:
  | nom    | email              | âge |
  | Alice  | alice@example.com  | 25  |
  | Bob    | bob@example.com    | 30  |
  | Charlie| charlie@example.com| 35  |

Table Verticale

Given un utilisateur avec les informations suivantes:
  | nom           | John Doe           |
  | email         | john@example.com   |
  | téléphone     | +33612345678       |
  | adresse       | 123 Rue de Paris   |
  | code postal   | 75001              |

Doc Strings (Chaînes Multi-lignes)

Pour les données textuelles volumineuses.

Scenario: Envoi d'un email
  Given un utilisateur connecté
  When l'utilisateur envoie un email avec le contenu:
    """
    Bonjour,

    Ceci est un email de test
    avec plusieurs lignes.

    Cordialement,
    John
    """
  Then l'email est envoyé avec succès

JSON / XML

Scenario: Création d'un utilisateur via API
  When je fais une requête POST à "/api/users" avec le body:
    """json
    {
      "name": "John Doe",
      "email": "john@example.com",
      "age": 30,
      "roles": ["user", "admin"]
    }
    """
  Then la réponse a le statut 201
  And la réponse contient l'id de l'utilisateur créé

Tags (Étiquettes)

Les tags permettent d'organiser et de filtrer les scénarios.

@authentification @critique
Feature: Connexion utilisateur

  @smoke @rapide
  Scenario: Connexion réussie
    Given l'utilisateur a des identifiants valides
    When l'utilisateur se connecte
    Then l'utilisateur est authentifié

  @sécurité
  Scenario: Connexion échouée avec mot de passe incorrect
    Given l'utilisateur a un email valide
    When l'utilisateur se connecte avec un mauvais mot de passe
    Then un message d'erreur est affiché

  @wip @ignore
  Scenario: Connexion avec authentification à deux facteurs
    # Work in progress - à implémenter

Utilisation des Tags

# Exécuter uniquement les tests @smoke
cucumber --tags @smoke

# Exécuter tous sauf @wip
cucumber --tags "not @wip"

# Exécuter @critique ET @authentification
cucumber --tags "@critique and @authentification"

# Exécuter @smoke OU @rapide
cucumber --tags "@smoke or @rapide"

Exemples Complets

E-commerce

@ecommerce @panier
Feature: Gestion du panier d'achat
  En tant que client
  Je veux gérer mon panier d'achat
  Afin de préparer ma commande

  Background:
    Given l'utilisateur est connecté
    And le catalogue contient les produits suivants:
      | id | nom          | prix  | stock |
      | 1  | Laptop       | 999   | 10    |
      | 2  | Souris       | 29    | 50    |
      | 3  | Clavier      | 79    | 30    |

  @ajout
  Scenario: Ajouter un article au panier vide
    Given le panier est vide
    When l'utilisateur ajoute le produit "Laptop" au panier
    Then le panier contient 1 article
    And le total du panier est de 999

  @quantité
  Scenario Outline: Modifier la quantité d'un article
    Given le panier contient 1 "Laptop"
    When l'utilisateur change la quantité à <quantité>
    Then le panier contient <quantité> "Laptop"
    And le total du panier est de <total>

    Examples:
      | quantité | total |
      | 2        | 1998  |
      | 3        | 2997  |
      | 0        | 0     |

  @suppression
  Scenario: Supprimer un article du panier
    Given le panier contient les articles suivants:
      | produit | quantité |
      | Laptop  | 1        |
      | Souris  | 2        |
    When l'utilisateur supprime "Laptop" du panier
    Then le panier contient 1 type d'article
    And le panier contient 2 "Souris"
    But le panier ne contient pas "Laptop"

API REST

@api @utilisateurs
Feature: API de gestion des utilisateurs
  En tant que développeur
  Je veux tester l'API utilisateurs
  Afin de garantir son bon fonctionnement

  Background:
    Given l'API est disponible à "http://localhost:8080"
    And j'ai un token d'authentification valide

  @get
  Scenario: Récupérer la liste des utilisateurs
    When je fais une requête GET à "/api/users"
    Then la réponse a le statut 200
    And la réponse contient une liste d'utilisateurs
    And chaque utilisateur a les champs suivants:
      | id    |
      | name  |
      | email |

  @post @création
  Scenario: Créer un nouvel utilisateur
    When je fais une requête POST à "/api/users" avec:
      """json
      {
        "name": "Alice Martin",
        "email": "alice@example.com",
        "password": "SecurePass123!"
      }
      """
    Then la réponse a le statut 201
    And la réponse contient l'id du nouvel utilisateur
    And un email de confirmation est envoyé à "alice@example.com"

  @put @modification
  Scenario: Modifier un utilisateur existant
    Given un utilisateur existe avec l'id 42
    When je fais une requête PUT à "/api/users/42" avec:
      """json
      {
        "name": "Alice Dupont"
      }
      """
    Then la réponse a le statut 200
    And l'utilisateur 42 a le nom "Alice Dupont"

  @delete @suppression
  Scenario: Supprimer un utilisateur
    Given un utilisateur existe avec l'id 42
    When je fais une requête DELETE à "/api/users/42"
    Then la réponse a le statut 204
    And l'utilisateur 42 n'existe plus

Implémentation avec Cucumber (Java)

Structure du Projet

src/
├── test/
│   ├── java/
│   │   ├── steps/
│   │   │   ├── AuthenticationSteps.java
│   │   │   └── CartSteps.java
│   │   ├── runners/
│   │   │   └── CucumberRunner.java
│   │   └── config/
│   │       └── TestConfig.java
│   └── resources/
│       └── features/
│           ├── authentication.feature
│           └── cart.feature

Step Definitions (Java)

import io.cucumber.java.en.Given;
import io.cucumber.java.en.When;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.And;

public class AuthenticationSteps {

    private User currentUser;
    private LoginPage loginPage;
    private HomePage homePage;

    @Given("l'utilisateur est sur la page de connexion")
    public void userIsOnLoginPage() {
        loginPage = new LoginPage();
        loginPage.open();
    }

    @Given("l'utilisateur a un compte avec l'email {string}")
    public void userHasAccountWithEmail(String email) {
        currentUser = userRepository.findByEmail(email);
        assertThat(currentUser).isNotNull();
    }

    @When("l'utilisateur saisit {string} dans le champ email")
    public void userEntersEmail(String email) {
        loginPage.enterEmail(email);
    }

    @When("l'utilisateur saisit {string} dans le champ mot de passe")
    public void userEntersPassword(String password) {
        loginPage.enterPassword(password);
    }

    @When("l'utilisateur clique sur le bouton {string}")
    public void userClicksButton(String buttonText) {
        loginPage.clickButton(buttonText);
    }

    @Then("l'utilisateur est redirigé vers la page d'accueil")
    public void userIsRedirectedToHomePage() {
        homePage = new HomePage();
        assertThat(homePage.isDisplayed()).isTrue();
    }

    @Then("un message {string} est affiché")
    public void messageIsDisplayed(String message) {
        assertThat(homePage.getWelcomeMessage()).contains(message);
    }
}

Cucumber Runner

import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
@CucumberOptions(
    features = "src/test/resources/features",
    glue = {"steps", "config"},
    plugin = {
        "pretty",
        "html:target/cucumber-reports/cucumber.html",
        "json:target/cucumber-reports/cucumber.json"
    },
    tags = "not @wip"
)
public class CucumberRunner {
}

Bonnes Pratiques

1. Écrire des Scénarios Déclaratifs

❌ Mauvais (impératif) :

Scenario: Connexion
  Given je suis sur "http://example.com/login"
  When je clique sur le champ "email"
  And je tape "john@example.com"
  And je clique sur le champ "password"
  And je tape "Password123!"
  And je clique sur le bouton "Submit"
  Then je vois "Welcome"

✅ Bon (déclaratif) :

Scenario: Connexion réussie
  Given l'utilisateur a des identifiants valides
  When l'utilisateur se connecte
  Then l'utilisateur voit la page d'accueil

2. Un Scénario = Un Comportement

# ❌ Mauvais - teste plusieurs choses
Scenario: Gestion complète du panier
  Given le panier est vide
  When j'ajoute un produit
  And je modifie la quantité
  And je supprime le produit
  And j'ajoute un autre produit
  Then le panier contient le nouveau produit

# ✅ Bon - scénarios séparés
Scenario: Ajouter un produit au panier
  Given le panier est vide
  When j'ajoute un produit
  Then le panier contient 1 produit

Scenario: Supprimer un produit du panier
  Given le panier contient 1 produit
  When je supprime le produit
  Then le panier est vide

3. Éviter les Détails Techniques

# ❌ Mauvais
Scenario: Créer un utilisateur
  When je fais un POST sur /api/users avec status 201
  And la base de données contient un enregistrement dans la table users

# ✅ Bon
Scenario: Créer un utilisateur
  When je crée un nouvel utilisateur
  Then l'utilisateur est enregistré dans le système

4. Utiliser le Background Judicieusement

# ✅ Bon usage du Background
Background:
  Given l'utilisateur est connecté

# ❌ Mauvais - trop spécifique
Background:
  Given l'utilisateur est connecté
  And le panier contient 3 articles
  And l'utilisateur a une carte de crédit enregistrée

5. Nommer Clairement les Scénarios

# ❌ Mauvais
Scenario: Test 1
Scenario: Vérification

# ✅ Bon
Scenario: Connexion réussie avec des identifiants valides
Scenario: Échec de connexion avec un mot de passe incorrect

Diagramme de Processus BDD

@startuml
|Business|
start
:Définir les exigences métier;
:Écrire les scénarios Gherkin;

|Développeur|
:Implémenter les step definitions;
:Développer le code;

|Testeur|
:Exécuter les tests Cucumber;

if (Tests passent?) then (oui)
  :Valider la fonctionnalité;
  stop
else (non)
  |Développeur|
  :Corriger le code;
  |Testeur|
  :Ré-exécuter les tests;
  endif
@enduml

Outils et Intégrations

Cucumber

  • Java : Cucumber-JVM
  • JavaScript : Cucumber.js
  • Ruby : Cucumber
  • Python : Behave
  • .NET : SpecFlow

IDEs

  • IntelliJ IDEA : Plugin Cucumber for Java
  • VS Code : Cucumber (Gherkin) Full Support
  • Eclipse : Cucumber Eclipse Plugin

CI/CD

# GitHub Actions
name: Cucumber Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run Cucumber tests
        run: mvn test
      - name: Publish test results
        uses: EnricoMi/publish-unit-test-result-action@v1
        with:
          files: target/cucumber-reports/*.xml

Conclusion

Gherkin est un outil puissant pour :

  • Collaboration : Langage commun entre métier et technique
  • Documentation : Tests qui documentent le comportement
  • Automatisation : Scénarios exécutables
  • Clarté : Spécifications lisibles et compréhensibles
  • Maintenabilité : Tests structurés et organisés

Utilisez Gherkin pour créer une documentation vivante qui garantit que votre application répond aux besoins métier.

Ressources