A pytest plugin that generates test stubs from Gherkin feature files, checks consistency, and displays BDD steps in pytest output
Project description
Generates pytest test stubs from Gherkin .feature files, checks consistency, and displays BDD steps — automatically, every time pytest runs.
pytest-beehave is a pytest plugin powered by beehave. It reads your Gherkin .feature files and generates Hypothesis-compatible test stubs with the right names, decorators, and parameters — then verifies that your test code stays consistent with your spec. New scenarios get @pytest.mark.skip; drift becomes a real test failure.
pip install pytest-beehave
No conftest.py changes required. The plugin registers itself via pytest's entry-point system.
Optional: install pytest-beehave[html] for a "Scenario" column in pytest-html reports.
pip install "pytest-beehave[html]"
Quick start
1. Write a feature file
# docs/features/checkout/shopping_cart.feature
Feature: Shopping cart
Background:
Given an empty cart
Rule: Tax calculation
Scenario: VAT is applied at the correct rate
Given a cart with items totalling $100
When the buyer is in the UK
Then the order total is $120
2. Run pytest
pytest --collect-only
3. A test stub is created
tests/features/shopping_cart/
├── tax_calculation_test.py
# tests/features/shopping_cart/tax_calculation_test.py
import pytest
@pytest.mark.skip(reason="not implemented")
def test_VAT_is_applied_at_the_correct_rate():
...
4. See BDD steps
pytest -v
tests/features/shopping_cart/tax_calculation_test.py::test_VAT_is_applied_at_the_correct_rate SKIPPED
Given an empty cart
Given a cart with items totalling $100
When the buyer is in the UK
Then the order total is $120
5. Implement and ship
Remove @pytest.mark.skip, write the test body, run pytest again. The steps display stays in sync with your feature file.
How it works
The plugin hooks into pytest_configure — every stub exists on disk before collection begins.
pytest invoked
└─ pytest_configure fires
├─ load_config() → read [tool.beehave] from pyproject.toml
├─ parse_feature() → parse .feature files into ScenarioInfo
├─ generate_stubs() → write Hypothesis test stubs to disk
├─ _add_skip_markers() → mark unimplemented stubs with @pytest.mark.skip
├─ check_all() → detect drift between features and tests
└─ register display plugins → StepsReporter (-v) and/or HtmlStepsPlugin
└─ pytest_collection_modifyitems → inject failing tests for ERROR violations
└─ Collection begins — every stub is already present
File layout
docs/features/ ← configured via features_dir
**/*.feature ← any subfolder structure is supported
tests/features/ ← configured via tests_dir
<feature_slug>/ ← one directory per feature (derived from Feature title)
<rule_slug>_test.py ← one file per Rule: block (or default_test.py)
Each test function name follows test_<scenario_title_with_underscores>. The mapping is exact string equality — no @id tags, no step definitions, no glue code.
What check_all enforces
After stub generation, the plugin runs check_all() to detect drift between feature files and test code. ERROR violations produce real test failures via synthetic test items:
| Type | Severity | What it catches |
|---|---|---|
unmapped-scenario |
ERROR | Scenario has no matching test function |
unmapped-test |
ERROR | Test function has no matching scenario |
misplaced-test |
WARNING | Function is in the wrong rule file |
missing-placeholder |
ERROR | Test body missing a <placeholder> |
missing-literal |
ERROR | Test body missing a "string" or numeric literal |
example-mismatch |
ERROR | Examples rows don't match @example() decorators |
$ pytest
[beehave] ERROR: tests/features/demo/default_test.py:5: unmapped-test: 'test_orphan' has no matching scenario
========================= 1 failed, 3 passed, 2 skipped =========================
Stub functions (body is ...) are excluded from placeholder and literal checks.
How it maps
- Scenario title → function name:
VAT Is Applied At The Correct Rate→test_VAT_is_applied_at_the_correct_rate. Lowercased. Globally unique. - Rule → test file: Top-level scenarios go to
default_test.py. Scenarios inside a Rule go to<rule>_test.py. - Feature title → directory:
Shopping Cart→tests/features/shopping_cart/. - Scenario Outline → decorators:
<placeholder>columns become@given()parameters with inferred Hypothesis strategies. Example rows become@example()decorators.
TDD workflow
pytest --collect-only→ stubs generated with@pytest.mark.skip- Remove
@pytest.mark.skip, write the test body → test runs and fails (red) - Fix the implementation → test passes (green)
- Add new scenarios to
.featurefiles → only new stubs get the skip marker
Configuration
All configuration lives under [tool.beehave] in pyproject.toml:
[tool.beehave]
features_dir = "docs/features" # default: docs/features
tests_dir = "tests/features" # default: tests/features
default_strategy = "text" # default: text (Hypothesis strategy for placeholders)
background_check_numeric = true # default: true
background_check_string = true # default: true
If features_dir does not exist, the plugin exits silently.
Requirements
| Version | |
|---|---|
| Python | >= 3.14 |
| pytest | >= 6.0 |
| beehave | >= 1.0.0 |
Contributing
git clone https://github.com/nullhack/pytest-beehave
cd pytest-beehave
uv sync --all-extras
uv run task test && uv run task lint && uv run task static-check
Bug reports and pull requests welcome on GitHub.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_beehave-1.0.0.tar.gz.
File metadata
- Download URL: pytest_beehave-1.0.0.tar.gz
- Upload date:
- Size: 13.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2d745027a58619a06568b3bdb2f88986be6bb4a863e2c5c6fe1a2824cdf2c71
|
|
| MD5 |
9f6cac2278ba9d166fbdf402205a76d2
|
|
| BLAKE2b-256 |
85295137c2909394ae2933e5be6aa034a7584c9e7e66b7731e46042408f7e6e4
|
Provenance
The following attestation bundles were made for pytest_beehave-1.0.0.tar.gz:
Publisher:
pypi-publish.yml on nullhack/pytest-beehave
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_beehave-1.0.0.tar.gz -
Subject digest:
e2d745027a58619a06568b3bdb2f88986be6bb4a863e2c5c6fe1a2824cdf2c71 - Sigstore transparency entry: 1587011276
- Sigstore integration time:
-
Permalink:
nullhack/pytest-beehave@7c540c2f862b3103803de27b4a3c998ea7af960e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nullhack
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@7c540c2f862b3103803de27b4a3c998ea7af960e -
Trigger Event:
workflow_run
-
Statement type:
File details
Details for the file pytest_beehave-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pytest_beehave-1.0.0-py3-none-any.whl
- Upload date:
- Size: 10.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2cbcfdf44fc6f328590e324901388731596f0d4c2ab402a593a79363e6b1bd8d
|
|
| MD5 |
acc8c00d5731e07700b02a3b975a745a
|
|
| BLAKE2b-256 |
8d606c0e9ca196f6634a0cc1654b6ea0f46e41269ad3b5868ceae241ed901e20
|
Provenance
The following attestation bundles were made for pytest_beehave-1.0.0-py3-none-any.whl:
Publisher:
pypi-publish.yml on nullhack/pytest-beehave
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_beehave-1.0.0-py3-none-any.whl -
Subject digest:
2cbcfdf44fc6f328590e324901388731596f0d4c2ab402a593a79363e6b1bd8d - Sigstore transparency entry: 1587011552
- Sigstore integration time:
-
Permalink:
nullhack/pytest-beehave@7c540c2f862b3103803de27b4a3c998ea7af960e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nullhack
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@7c540c2f862b3103803de27b4a3c998ea7af960e -
Trigger Event:
workflow_run
-
Statement type: