behave / pytest-bdd Setup
behave and pytest-bdd connect Gherkin feature files to Python step definitions. pytest-bdd integrates with existing pytest suites; behave is a standalone BDD runner.
Recipe
# pytest-bdd (recommended with pytest)
uv add --group dev pytest-bdd
# behave (standalone)
uv add --group dev behaveproject/
├── features/
│ ├── invoice.feature
│ └── steps/
│ └── invoice_steps.py
└── tests/
└── test_bdd.py
When to reach for this:
- Adding BDD to an existing pytest project (pytest-bdd)
- Greenfield BDD with dedicated runner (behave)
- Connecting feature files to FastAPI/Django test clients
Working Example
# features/invoice.feature
Feature: Invoice API
Scenario: Create draft invoice
Given the API is running
When I POST /invoices with amount 100.00
Then the response status is 201# features/steps/invoice_steps.py (pytest-bdd)
from pytest_bdd import scenarios, given, when, then, parsers
from fastapi.testclient import TestClient
from myapp.main import app
scenarios("../invoice.feature")
client = TestClient(app)
_context = {}
@given("the API is running")
def api_running():
_context["client"] = client
@when(parsers.parse("I POST /invoices with amount {amount:f}"))
def post_invoice(amount):
_context["response"] = _context["client"].post(
"/invoices", json={"amount": amount, "currency": "USD"}
)
@then(parsers.parse("the response status is {status:d}"))
def check_status(status):
assert _context["response"].status_code == statusuv run pytest features/steps/invoice_steps.py -vWhat this demonstrates:
- Feature file in
features/directory scenarios()links feature to step module@given/@when/@thendecorators map Gherkin to Pythonparsers.parseextracts parameters from step text
Deep Dive
pytest-bdd vs behave
| Aspect | pytest-bdd | behave |
|---|---|---|
| Runner | pytest | behave CLI |
| Fixtures | pytest fixtures in steps | behave context object |
| Integration | Same suite as unit tests | Separate features/ runner |
| Parser | @given(parsers.parse(...)) | @when('{amount:f}') parse format |
behave Setup
features/
├── environment.py # hooks: before_all, after_scenario
├── invoice.feature
└── steps/
└── invoice_steps.py
uv run behave features/Gotchas
- Global context dict - state leaks between scenarios. Fix: use pytest fixtures or behave
contextper scenario. - scenarios() path wrong - no tests collected. Fix: path relative to the steps file.
- Step text mismatch - step not found error. Fix: Gherkin text must match decorator exactly (or use parsers).
- Running features without step files - 0 tests. Fix: ensure
scenarios()import in a collected test module. - behave and pytest-bdd mixed - two runners, duplicate steps. Fix: pick one tool per project.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Plain pytest | No stakeholder-readable specs | BDD process adopted |
| Robot Framework | Keyword-driven, non-dev authors | Python-native steps preferred |
FAQs
pytest-bdd or behave?
pytest-bdd if already on pytest. behave for standalone BDD projects.
Where do feature files go?
features/ at project root is the convention for both tools.
Can I use pytest fixtures in steps?
Yes with pytest-bdd. Request fixtures as step function parameters.
How do I run one feature?
pytest-bdd: pytest -k scenario_name. behave: behave features/invoice.feature.
How do I share steps across features?
Import step modules or put common steps in features/steps/common_steps.py.
Does pytest-bdd support Scenario Outline?
Yes. Examples table maps to parametrized scenarios automatically.
How do I set up behave environment hooks?
features/environment.py with before_scenario, after_all, etc.
Can I test Django with BDD?
Yes. Use Django test client in step definitions with pytest-django.
How do I tag scenarios?
@smoke above Scenario. Filter: behave --tags=smoke or pytest markers.
How do I debug failing steps?
pytest --pdb or add logging in step definitions. behave: --no-capture.
Related
- Gherkin Syntax - writing features
- Step Definitions - step patterns
- Gherkin to CI Pipeline - CI integration
- pytest Setup - pytest config
Stack versions: This page was written for Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.