Skip to main content

A pytest plugin that generates test stubs from Gherkin feature files, checks consistency, and displays BDD steps in pytest output

Project description

pytest-beehave

Python License PyPI CI

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 Ratetest_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 Carttests/features/shopping_cart/.
  • Scenario Outline → decorators: <placeholder> columns become @given() parameters with inferred Hypothesis strategies. Example rows become @example() decorators.

TDD workflow

  1. pytest --collect-only → stubs generated with @pytest.mark.skip
  2. Remove @pytest.mark.skip, write the test body → test runs and fails (red)
  3. Fix the implementation → test passes (green)
  4. Add new scenarios to .feature files → 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest_beehave-1.0.0.tar.gz (13.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytest_beehave-1.0.0-py3-none-any.whl (10.2 kB view details)

Uploaded Python 3

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

Hashes for pytest_beehave-1.0.0.tar.gz
Algorithm Hash digest
SHA256 e2d745027a58619a06568b3bdb2f88986be6bb4a863e2c5c6fe1a2824cdf2c71
MD5 9f6cac2278ba9d166fbdf402205a76d2
BLAKE2b-256 85295137c2909394ae2933e5be6aa034a7584c9e7e66b7731e46042408f7e6e4

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_beehave-1.0.0.tar.gz:

Publisher: pypi-publish.yml on nullhack/pytest-beehave

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for pytest_beehave-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2cbcfdf44fc6f328590e324901388731596f0d4c2ab402a593a79363e6b1bd8d
MD5 acc8c00d5731e07700b02a3b975a745a
BLAKE2b-256 8d606c0e9ca196f6634a0cc1654b6ea0f46e41269ad3b5868ceae241ed901e20

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page