Behavior Driven Development BDD
Behavior-Driven Development (BDD)
BDD beschreibt Software-Verhalten in natürlicher Sprache. Lernen Sie Gherkin, Cucumber und wie BDD die Zusammenarbeit zwischen Entwicklern und Business verbessert.
BDD vs TDD
┌─────────────────────────────────────────────────────────────┐ │ TDD vs BDD │ ├─────────────────────────────────────────────────────────────┤ │ │ │ TDD (Test-Driven Development) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ • Fokus auf TECHNISCHE Implementation │ │ │ │ • Unit Tests in Code │ │ │ │ • Von Entwicklern geschrieben │ │ │ │ • expect(calculator.add(2, 3)).toBe(5) │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ BDD (Behavior-Driven Development) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ • Fokus auf GESCHÄFTSVERHALTEN │ │ │ │ • Szenarien in natürlicher Sprache │ │ │ │ • Von allen Stakeholdern verstehbar │ │ │ │ • Given I have 2 items, When I add 3, Then I have 5│ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ BDD baut auf TDD auf, nicht ersetzt es! │ │ │ └─────────────────────────────────────────────────────────────┘
Gherkin Syntax
# login.feature
Feature: User Login
As a registered user
I want to log into my account
So that I can access my personal dashboard
Background:
Given a user exists with email "john@example.com" and password "secure123"
Scenario: Successful login with valid credentials
Given I am on the login page
When I enter "john@example.com" as email
And I enter "secure123" as password
And I click the login button
Then I should be redirected to the dashboard
And I should see "Welcome, John"
Scenario: Failed login with wrong password
Given I am on the login page
When I enter "john@example.com" as email
And I enter "wrongpassword" as password
And I click the login button
Then I should see an error message "Invalid credentials"
And I should remain on the login page
Scenario: Account lockout after multiple failed attempts
Given I am on the login page
When I enter "john@example.com" as email
And I fail to login 3 times
Then my account should be locked
And I should see "Account locked. Please contact support."
Gherkin Keywords
| Keyword | Beschreibung | Beispiel |
|---|---|---|
| Feature | Beschreibt das Feature | Feature: Shopping Cart |
| Scenario | Ein konkreter Testfall | Scenario: Add item to cart |
| Given | Vorbedingung (Arrange) | Given I have an empty cart |
| When | Aktion (Act) | When I add a product |
| Then | Erwartetes Ergebnis (Assert) | Then cart should have 1 item |
| And/But | Zusätzliche Schritte | And total should be $10 |
| Background | Gemeinsame Vorbedingungen | Background: Given I am logged in |
Scenario Outline (Parametrisiert)
# discount.feature
Feature: Discount Calculation
Scenario Outline: Apply discount based on order amount
Given I have items worth <amount> in my cart
When I proceed to checkout
Then I should receive a <discount>% discount
And my total should be <total>
Examples:
| amount | discount | total |
| 50 | 0 | 50.00 |
| 100 | 5 | 95.00 |
| 200 | 10 | 180.00 |
| 500 | 15 | 425.00 |
Cucumber.js Implementation
// features/step_definitions/login.steps.js
const { Given, When, Then, Before } = require('@cucumber/cucumber');
const { expect } = require('chai');
let app, currentPage, user;
Before(function () {
app = new TestApp();
currentPage = null;
});
// Background Step
Given('a user exists with email {string} and password {string}', async function (email, password) {
user = await app.createUser({ email, password });
});
// Given Steps
Given('I am on the login page', async function () {
currentPage = await app.navigateTo('/login');
});
// When Steps
When('I enter {string} as email', async function (email) {
await currentPage.fill('#email', email);
});
When('I enter {string} as password', async function (password) {
await currentPage.fill('#password', password);
});
When('I click the login button', async function () {
await currentPage.click('#login-button');
});
When('I fail to login {int} times', async function (times) {
for (let i = 0; i < times; i++) {
await currentPage.fill('#email', user.email);
await currentPage.fill('#password', 'wrong');
await currentPage.click('#login-button');
}
});
// Then Steps
Then('I should be redirected to the dashboard', async function () {
expect(currentPage.url()).to.include('/dashboard');
});
Then('I should see {string}', async function (text) {
const content = await currentPage.textContent('body');
expect(content).to.include(text);
});
Then('I should see an error message {string}', async function (message) {
const error = await currentPage.textContent('.error-message');
expect(error).to.equal(message);
});
Then('I should remain on the login page', async function () {
expect(currentPage.url()).to.include('/login');
});
Then('my account should be locked', async function () {
const updatedUser = await app.getUser(user.id);
expect(updatedUser.isLocked).to.be.true;
});
Behat (PHP)
// features/bootstrap/FeatureContext.php
use Behat\Behat\Context\Context;
use PHPUnit\Framework\Assert;
class FeatureContext implements Context
{
private App $app;
private ?User $user = null;
private ?Page $currentPage = null;
public function __construct()
{
$this->app = new TestApp();
}
/**
* @Given a user exists with email :email and password :password
*/
public function aUserExistsWithEmailAndPassword(string $email, string $password): void
{
$this->user = $this->app->createUser($email, $password);
}
/**
* @Given I am on the login page
*/
public function iAmOnTheLoginPage(): void
{
$this->currentPage = $this->app->navigateTo('/login');
}
/**
* @When I enter :value as email
*/
public function iEnterAsEmail(string $value): void
{
$this->currentPage->fill('#email', $value);
}
/**
* @When I enter :value as password
*/
public function iEnterAsPassword(string $value): void
{
$this->currentPage->fill('#password', $value);
}
/**
* @When I click the login button
*/
public function iClickTheLoginButton(): void
{
$this->currentPage->click('#login-button');
}
/**
* @Then I should be redirected to the dashboard
*/
public function iShouldBeRedirectedToTheDashboard(): void
{
Assert::assertStringContainsString('/dashboard', $this->currentPage->getUrl());
}
/**
* @Then I should see :text
*/
public function iShouldSee(string $text): void
{
Assert::assertStringContainsString($text, $this->currentPage->getContent());
}
/**
* @Then I should see an error message :message
*/
public function iShouldSeeAnErrorMessage(string $message): void
{
$error = $this->currentPage->find('.error-message')->getText();
Assert::assertEquals($message, $error);
}
}
E-Commerce Feature Beispiel
# features/shopping_cart.feature
Feature: Shopping Cart
As a customer
I want to manage items in my shopping cart
So that I can purchase products I want
Background:
Given the following products exist:
| name | price | stock |
| Laptop | 999.00 | 10 |
| Mouse | 29.99 | 50 |
| Keyboard | 79.99 | 25 |
Scenario: Add product to empty cart
Given I have an empty cart
When I add "Laptop" to my cart
Then my cart should contain 1 item
And my cart total should be $999.00
Scenario: Add multiple quantities of same product
Given I have an empty cart
When I add 2 "Mouse" to my cart
Then my cart should contain 2 items
And my cart total should be $59.98
Scenario: Cannot add more than available stock
Given I have an empty cart
When I try to add 15 "Laptop" to my cart
Then I should see an error "Only 10 items available"
And my cart should be empty
Scenario: Apply coupon code
Given I have the following items in my cart:
| product | quantity |
| Laptop | 1 |
| Mouse | 2 |
When I apply coupon code "SAVE10"
Then I should receive a 10% discount
And my cart total should be $953.08
Scenario: Remove item from cart
Given I have the following items in my cart:
| product | quantity |
| Laptop | 1 |
| Mouse | 1 |
When I remove "Mouse" from my cart
Then my cart should contain 1 item
And my cart should not contain "Mouse"
BDD Workflow
┌─────────────────────────────────────────────────────────────┐ │ BDD WORKFLOW │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. DISCOVERY (Three Amigos Meeting) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 👔 Business + 👨💻 Developer + 🧪 QA │ │ │ │ │ │ │ │ • Was soll das Feature tun? │ │ │ │ • Welche Szenarien gibt es? │ │ │ │ • Was sind die Akzeptanzkriterien? │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ 2. FORMULATION (Gherkin schreiben) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Feature Files in natürlicher Sprache │ │ │ │ Von allen verstanden und reviewt │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ 3. AUTOMATION (Step Definitions) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Developer implementiert Steps │ │ │ │ Tests sind ausführbar │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ 4. IMPLEMENTATION │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Feature entwickeln bis alle Szenarien grün │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘
Gute Gherkin-Szenarien schreiben
// ❌ SCHLECHT: Zu technisch, zu detailliert Scenario: Login Given I open browser And I navigate to "http://localhost:3000/login" And I wait for page to load When I find element with id "email" And I type "john@example.com" And I find element with id "password" And I type "secret123" And I find element with id "submit" And I click it Then I should see element with class "dashboard" // ✅ GUT: Business-Sprache, lesbar Scenario: Successful login Given I am a registered user When I log in with valid credentials Then I should see my dashboard
// ❌ SCHLECHT: Zu viele Details in einem Szenario Scenario: Complete order Given I am logged in And I have product A in cart And I have product B in cart And I have applied coupon "SAVE10" And I have entered shipping address And I have selected express shipping And I have entered credit card details When I click place order Then order should be created And payment should be processed And confirmation email should be sent And inventory should be updated // ✅ GUT: Fokussiert auf ein Verhalten Scenario: Place order with valid payment Given I have items in my cart And I have entered valid shipping and payment details When I place my order Then I should receive an order confirmation // Separate Szenarien für andere Aspekte: Scenario: Inventory is updated after order Scenario: Confirmation email is sent after order
Tools
CUCUMBER IMPLEMENTIERUNGEN ┌──────────────┬────────────────────────┬─────────────────────┐ │ Sprache │ Tool │ Installation │ ├──────────────┼────────────────────────┼─────────────────────┤ │ JavaScript │ Cucumber.js │ npm i @cucumber/ │ │ │ │ cucumber │ ├──────────────┼────────────────────────┼─────────────────────┤ │ PHP │ Behat │ composer require │ │ │ │ behat/behat │ ├──────────────┼────────────────────────┼─────────────────────┤ │ Java │ Cucumber-JVM │ Maven/Gradle │ ├──────────────┼────────────────────────┼─────────────────────┤ │ Python │ Behave │ pip install behave │ ├──────────────┼────────────────────────┼─────────────────────┤ │ Ruby │ Cucumber │ gem install cucumber│ └──────────────┴────────────────────────┴─────────────────────┘ UI TESTING INTEGRATION • Playwright + Cucumber • Cypress + cucumber-preprocessor • Selenium + Cucumber
💡 Best Practices:
1. Business-Sprache verwenden, nicht technische Details
2. Ein Szenario = ein Verhalten
3. Three Amigos: Business, Dev, QA gemeinsam
4. Feature Files sind lebende Dokumentation
5. Steps wiederverwendbar gestalten
2. Ein Szenario = ein Verhalten
3. Three Amigos: Business, Dev, QA gemeinsam
4. Feature Files sind lebende Dokumentation
5. Steps wiederverwendbar gestalten