Comprehensive BDD testing with Cucumber and Gherkin syntax. Use when writing feature files (.feature), step definitions, hooks, or implementing Behaviour-Driven Development.
BDD testing with plain-text executable specifications.
This skill is for:
.feature filesKeep it lean. Prefer Python examples here. Skip Ruby/JavaScript noise unless the user asks.
Cucumber reads steps in Gherkin and matches them to step definitions that drive the system under test.
┌────────────┐ ┌──────────────┐ ┌───────────┐
│ Steps │ │ Step │ │ │
│ in Gherkin ├──matched with──>│ Definitions ├───manipulates──>│ System │
└────────────┘ └──────────────┘ └───────────┘
Feature: Short description
Optional multi-line description explaining the feature.
Background:
Given common setup steps for all scenarios
Rule: Business rule grouping
Scenario: Concrete example illustrating the rule
Given an initial context
When an action occurs
Then expected outcome
And additional step
But negative assertion
Scenario Outline: Parameterized scenario
Given there are <start> items
When I remove <remove> items
Then I should have <remaining> items
Examples:
| start | remove | remaining |
| 12 | 5 | 7 |
| 20 | 5 | 15 |
Given — setup / preconditionWhen — action / triggerThen — expected outcome / assertionAnd — continue previous step typeBut — negative continuation* — bullet-style stepExamples:
Given I am logged in as "admin"
When I submit the form
Then I should see "Success"
And I should see my dashboard
But I should not see "Error"
* I have eggs
Given the following users exist:
| name | email | role |
| Alice | [email protected] | admin |
| Bob | [email protected] | user |
Given a blog post with content:
"""markdown
# My Post Title
This is the content of my blog post.
"""
Use tags to slice runs and attach special setup.
@smoke @critical
Feature: User authentication
@wip
Scenario: Login with valid credentials
Given a valid user exists
When the user logs in
Then the dashboard is shown
@slow @database
Scenario: Bulk user import
Given a CSV file exists
When the import runs
Then all valid users are created
Common tag expressions:
@smoke and not @slow@gui or @api(@smoke or @critical) and not @wipfrom pytest_bdd import scenarios, given, when, then, parsers
scenarios("features/login.feature")
@given("a valid user exists")
def valid_user(db):
db.create_user(username="bob", password="secret")
@when(parsers.parse('the user logs in as "{username}" with password "{password}"'))
def log_in(client, username, password):
client.login(username=username, password=password)
@then("the dashboard is shown")
def dashboard_is_shown(client):
assert client.page_title() == "Dashboard"
Bad. Bad. Bad.
If pytest-bdd cannot see the step module, your feature will fail with StepDefinitionNotFoundError even though the decorator exists.
Use one of these patterns:
scenarios(...)tests/conftest.pyExample:
# tests/test_bdd_features.py
from tests.bdd.steps import host_steps # noqa: F401
from pytest_bdd import scenarios
scenarios("bdd/robot_host.feature")
If a Given step needs to feed later steps as a fixture, use target_fixture:
@given("the host is running", target_fixture="active_host")
def given_host_is_running(running_host):
return running_host
Without target_fixture, later steps may not be able to request that value by fixture name.
from behave import given, when, then
@given("a valid user exists")
def step_valid_user(context):
context.user = create_user("bob", "secret")
@when("the user logs in")
def step_login(context):
context.response = context.client.login("bob", "secret")
@then("the dashboard is shown")
def step_dashboard(context):
assert "Dashboard" in context.response.text
Prefer short hooks. Tag-scoped hooks beat giant global magic.
# features/environment.py
def before_scenario(context, scenario):
context.client = make_test_client()
def after_scenario(context, scenario):
context.client.close()
Use pytest fixtures for setup/teardown instead of trying to stuff everything into hooks.
import pytest
@pytest.fixture
def client():
client = make_test_client()
yield client
client.close()
Good:
When "Bob" logs in
Then he sees his dashboard
Avoid:
When I visit "/login"
And I enter "bob" in "username"
And I enter "secret" in "password"
And I click "Login"
Then I should see "Dashboard"
Background short, 4 lines maxUse Scenario Outline only when the same behavior truly repeats with different examples. If examples tell different stories, split them.
For hosts, daemons, and ZMQ services, do not force every BDD scenario through real background threads if the behavior can be tested directly.
A better pattern:
apply_action() and publish_observation()This avoids flaky shutdown bugs, port collisions, and hanging tests while still testing the real behavior.
Bad. Bad. Bad.
When I do the thingThenpytest -q
pytest tests/test_login.py -q
pytest -k smoke -q
behave
behave features/login.feature
behave --tags=@smoke
Write feature files for humans first. Write step definitions for machines second. If the feature reads like Selenium fan fiction, you screwed it up.