🎯 OBJECTIF
Comprendre comment :
@CucumberContextConfiguration@ScenarioScope🧠 MODÈLE MENTAL
Cucumber n'est pas un outil de test — c'est un outil de documentation vivante. Les fichiers .feature Gherkin sont écrits en langage métier, lisibles par un PO ou un QA sans lire une ligne de Java. Les step definitions Java sont l'implémentation cachée. Quand un scénario passe, c'est la preuve que le comportement métier fonctionne exactement comme documenté.
La position dans la pyramide est critique : Cucumber teste des flux critiques bout-en-bout, pas des cas edge. Un scénario Cucumber qui remplace dix tests unitaires est un anti-pattern — les tests unitaires restent bien meilleurs pour explorer les cas limites et les branches conditionnelles. La valeur de Cucumber est dans l'alignement PO/QA/Dev et dans la validation des scénarios métier de haut niveau, pas dans la couverture exhaustive.
Feature: Gestion des stocks
Background:
Given une adresse "A1" existe dans le système
Scenario: Déplacer des stocks entre adresses
Given l'adresse "A1" a 10 unités du produit "P1"
When je déplace 5 unités vers l'adresse "A2"
Then l'adresse "A1" doit avoir 5 unités
And l'adresse "A2" doit avoir 5 unités
Scenario Outline: Retrait partiel
Given un solde de <solde>
When je retire <montant>
Then le résultat est <résultat>
Examples:
| solde | montant | résultat |
| 100 | 30 | 70 |
| 50 | 10 | 40 |
🚨 Écrire les steps en langage technique = anti-pattern
# ❌ INTERDIT — technique, incompréhensible pour un PO
Given j'appelle POST /stocks avec le payload {"productId": "P1", "quantity": 10}
# ✓ CORRECT — métier, lisible par tous
Given le stock "P1" existe avec 10 unités en adresse "A1"
| Keyword | Rôle |
|---|---|
Feature |
Contexte métier de la fonctionnalité |
Background |
Étapes exécutées avant chaque scénario |
Scenario |
Cas métier isolé |
Given |
État initial |
When |
Action déclenchée |
Then |
Résultat observable |
Dépendances Maven :
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<scope>test</scope>
</dependency>xmlClasse de configuration obligatoire :
@CucumberContextConfiguration
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CucumberSpringConfig {
// aucun contenu — la classe sert de pont entre Cucumber et Spring
}java@CucumberContextConfiguration permet à Cucumber d'utiliser Spring comme conteneur DI. RANDOM_PORT démarre un vrai serveur Tomcat — les steps peuvent faire des vraies requêtes HTTP.
@Component
@ScenarioScope // ✓ bean recréé pour chaque scénario
public class TestContext {
private int responseStatus;
private String responseBody;
// getters/setters
}java@Component
@RequiredArgsConstructor
public class StockSteps {
private final WebTestClient webTestClient;
private final TestContext ctx; // ✓ injecté par Spring
@Given("le stock {string} existe avec {int} unités")
public void stockExists(String productId, int quantity) {
// setup en DB ou via API
}
@When("je récupère les stocks de l'adresse {string}")
public void getStocks(String addressId) {
var result = webTestClient.get()
.uri("/stocks?addressId=" + addressId)
.exchange()
.expectBody()
.returnResult();
ctx.setResponseStatus(result.getStatus().value());
ctx.setResponseBody(new String(result.getResponseBody()));
}
@Then("le code de retour est {int}")
public void checkStatus(int expectedStatus) {
assertEquals(expectedStatus, ctx.getResponseStatus());
}
}java🚨 Ne jamais utiliser de variables statiques pour partager l'état
Les scénarios s'exécutent séquentiellement mais le contexte doit être propre entre chaque scénario. Un static Map<String, Object> context contamine les scénarios suivants. Toujours utiliser @ScenarioScope.
@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@CucumberContextConfiguration
public class CucumberTestConfig {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15");
@Container
static KafkaContainer kafka =
new KafkaContainer("confluentinc/cp-kafka:7.5.0");
@DynamicPropertySource
static void register(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
}
}javaTester un événement Kafka :
@Then("un événement StockCreated doit être publié")
public void verifyKafkaEvent() {
ConsumerRecord<String, Object> record = kafkaConsumer.poll(Duration.ofSeconds(5));
assertNotNull(record);
assertEquals("StockCreated", record.value().getType());
}javaScenario: Créer plusieurs stocks en lot
Given les stocks suivants existent
| produit | adresse | quantité |
| P1 | A1 | 10 |
| P2 | A2 | 5 |
@Given("les stocks suivants existent")
public void createStocks(DataTable table) {
List<Map<String, String>> rows = table.asMaps();
rows.forEach(row -> {
String product = row.get("produit");
String address = row.get("adresse");
int quantity = Integer.parseInt(row.get("quantité"));
// créer le stock
});
}javasrc/test/resources/
features/
stock/
stock-movement.feature
stock-creation.feature
order/
order-placement.feature
src/test/java/
cucumber/
config/
CucumberSpringConfig.java
context/
TestContext.java ← @ScenarioScope
steps/
StockSteps.java
OrderSteps.java
flowchart TD
U["Tests unitaires\n~70%\nLogique métier, branches, cas edge"]
I["Tests d'intégration\n~25%\n@WebMvcTest, @DataJpaTest"]
C["Cucumber\n~5%\nFlux métier critiques bout-en-bout"]
U --> I --> CmermaidCucumber pour : la documentation vivante des flux critiques, l'alignement PO/QA/Dev, les scénarios de bout-en-bout qui traversent toutes les couches.
Pas Cucumber pour : remplacer les tests unitaires, tester chaque cas edge, chaque branche conditionnelle, chaque validation individuelle.
⚡ TL;DR — chaque concept en une ligne
Gherkin ✓ DSL métier lisible par tous — Feature/Scenario/Given/When/Then. Les steps décrivent le comportement, pas l'implémentation. ⚠ Dès qu'un step contient une URL, un payload JSON ou un nom de classe Java, c'est un anti-pattern — réécrire en langage métier.
@CucumberContextConfiguration ✓ Pont entre Cucumber et Spring — permet l'injection de dépendances Spring dans les step definitions. ⚠ Sans cette annotation, Cucumber ne sait pas utiliser Spring comme DI container.
@ScenarioScope
✓ Bean recréé à chaque scénario — isole l'état entre scénarios, évite la contamination.
⚠ static ou singleton pour partager l'état = contamination garantie entre scénarios.
Positionnement ✓ Documentation vivante + validation des flux critiques — valeur dans l'alignement métier/dev. ⚠ Cucumber ne remplace pas les tests unitaires — il est en haut de la pyramide, pas à la base.
🎓 À retenir
@ScenarioScope sur tout objet d'état partagé — pas de champ static, pas de @Component singleton pour stocker l'état entre steps. Chaque scénario doit démarrer avec un état propre.static pour la performance — un PostgreSQLContainer static partagé entre tous les scénarios de la suite. Sans static, Docker redémarre pour chaque scénario — la suite devient inutilisable en termes de temps.