Skip to main content

A thin layer on Hypothesis for Gherkin-style BDD testing with vocabulary enforcement

Project description

beehave

Python License PyPI

Keep your living documentation and test code in sync — without the step-definition boilerplate.


beehave is a simpler alternative to behave and pytest-bdd. Instead of writing step definitions that match Gherkin step text to Python functions with @given/@when/@then decorators, beehave links scenarios to tests by function name alone. It generates pure Hypothesis property-based test stubs from your .feature files and checks that your test code stays consistent with your spec. Your tests never import beehave — they just use hypothesis.

pip install beehave

How it differs from standard Gherkin tools

Traditional BDD frameworks (behave, pytest-bdd) require step definitions — separate Python functions decorated with @given/@when/@then whose text must match the Gherkin step text exactly. This creates fragile coupling, boilerplate, and framework lock-in. beehave eliminates all of that:

  • No step definitions. The function name is the link. Scenario: guard bee inspects visitortest_guard_bee_inspects_visitor.
  • No runtime imports. Your tests import only hypothesis. beehave is a dev-time CLI.
  • Property-based by default. Hypothesis @given() strategies are inferred from Examples table types. behave and pytest-bdd are example-only.

To make this work, beehave applies a few constraints beyond standard Gherkin:

Constraint Why
Titles contain only letters, digits, and spaces They become Python identifiers (test_...) and file paths
<placeholder> names must be valid Python identifiers, not keywords or builtins They become function parameters
"quoted strings" and bare numbers in step text are enforced literals check verifies they appear as Constant nodes in the function body
Scenario titles are globally unique across all features One function name = one scenario, everywhere

Usage

Write a feature

# docs/features/hive_activity.feature
Feature: Hive Activity

  Background:
    Given the hive is active

  Scenario Outline: honey production from nectar
    Given the hive has <nectar> grams of nectar
    And the evaporation rate is <rate> percent
    When the bees fan their wings for <hours> hours
    Then the hive produces <honey> grams of honey

    Examples:
      | nectar | rate | hours | honey |
      | 100    | 20   | 8     | 80    |
      | 200    | 25   | 12    | 150   |
      | 50     | 30   | 6     | 35    |

  Rule: Hive defense

    Background:
      Given the entrance has 2 guards

    Scenario: guard bee inspects visitor
      Given a visitor bee with <scent> colony odor
      When the guard inspects the visitor for "floral" scent
      Then the visitor is <outcome>

  Rule: Foraging

    Scenario: forager returns with nectar
      Given a forager bee named <name>
      When the forager returns with <volume> milliliters of nectar
      Then the hive stores <volume> milliliters of nectar

Generate stubs

beehave generate hive_activity
tests/features/hive_activity/
├── default_test.py        # top-level scenarios (honey production outline)
├── hive_defense_test.py   # Rule: Hive defense (guard bee)
└── foraging_test.py       # Rule: Foraging (forager returns)
# tests/features/hive_activity/default_test.py
from hypothesis import given, example, strategies as st

@example(nectar=100, rate=20, hours=8, honey=80)
@example(nectar=200, rate=25, hours=12, honey=150)
@example(nectar=50, rate=30, hours=6, honey=35)
@given(nectar=st.integers(), rate=st.integers(), hours=st.integers(), honey=st.integers())
def test_honey_production_from_nectar(nectar, rate, hours, honey):
    ...
# tests/features/hive_activity/hive_defense_test.py
from hypothesis import given, strategies as st

@given(scent=st.text(), outcome=st.text())
def test_guard_bee_inspects_visitor(scent, outcome):
    ...

Note what beehave extracted automatically:

  • <nectar>, <rate>@given() parameters. Strategies inferred from Examples table types (all integers → st.integers()).
  • 100, 20@example() rows from the Examples table.
  • "floral" → enforced literal from step text. check verifies it appears in the function body.
  • 2 (from Rule Background 2 guards) → enforced literal, inherited by all scenarios in that Rule.
  • <scent>, <outcome>@given() parameters. No Examples table, so strategy falls back to st.text().

Check consistency

You implement the guard test:

@given(scent=st.text(), outcome=st.text())
def test_guard_bee_inspects_visitor(scent, outcome):
    assert "floral" in known_scents()
    assert 2 == guard_count()
    assert scent in ("floral", "citrus")
    assert outcome in ("admitted", "rejected")
beehave check hive_activity    # check one feature
beehave check                  # check all features

Remove the "floral" assertion and check catches it:

tests/features/hive_activity/hive_defense_test.py:4: missing-literal: literal '"floral"' not found in function body

Remove <scent> from the body but keep it as a @given() parameter? Still caught — beehave checks the body only:

tests/features/hive_activity/hive_defense_test.py:4: missing-placeholder: 'scent' not found in function body

Rename the scenario? Both sides are reported:

docs/features/hive_activity.feature:22: unmapped-scenario: scenario 'guard checks visitor' has no test function
tests/features/hive_activity/hive_defense_test.py:4: unmapped-test: 'test_guard_bee_inspects_visitor' has no matching scenario

Clean up stale functions

beehave clean hive_activity           # remove unmapped stubs only (safe)
beehave clean hive_activity --force   # remove any unmapped function

List features

beehave list          # paths and titles
beehave list -v       # include scenario counts, rules, stub status

What check enforces

Check Severity What it catches
unmapped-scenario error Scenario has no matching test function
unmapped-test error Test function has no matching scenario
missing-placeholder error <placeholder> not referenced in function body
missing-literal error "string" or numeric literal not in function body
example-mismatch error Examples row has no matching @example() or vice versa
misplaced-test warning Function in wrong file (e.g., after Rule removal)

Warnings exit 0. Errors exit 1. Stubs (bodies with only pass or ...) skip body enforcement until you implement them.


How it maps

  • Scenario title → function name: Honey Production From Nectartest_honey_production_from_nectar. Lowercased. Globally unique across all features.
  • Rule → test file: Top-level scenarios go to default_test.py. Scenarios inside a Rule go to <rule>_test.py.
  • Feature title → directory: Hive Activitytests/features/hive_activity/.
  • Strategy inference: Examples table column values are typed — all integers → st.integers(), all floats → st.floats(), all booleans → st.booleans(), else → st.text().
  • Background merging: Feature Background applies to all scenarios. Rule Background applies to that Rule's scenarios only. Background steps cannot contain <placeholders>.
  • Literal extraction: "quoted strings" and numeric tokens in step text are enforced as Constant AST nodes in the function body.

Configuration

# pyproject.toml
[tool.beehave]
features_dir = "docs/features"
tests_dir = "tests/features"
default_strategy = "text"
background_check_numeric = true
background_check_string = true
Option Default Description
features_dir docs/features Where .feature files live
tests_dir tests/features Where generated tests go
default_strategy text Fallback strategy for unknown placeholders
background_check_numeric true Enforce numeric literals from Background steps
background_check_string true Enforce string literals from Background steps

License

MIT

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

beehave-0.4.0.tar.gz (55.1 kB view details)

Uploaded Source

Built Distribution

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

beehave-0.4.0-py3-none-any.whl (17.2 kB view details)

Uploaded Python 3

File details

Details for the file beehave-0.4.0.tar.gz.

File metadata

  • Download URL: beehave-0.4.0.tar.gz
  • Upload date:
  • Size: 55.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for beehave-0.4.0.tar.gz
Algorithm Hash digest
SHA256 f0a17b4964b6d8f38f1a378032bf942edd31c364a8c3ca74f6a871e46ab40275
MD5 a544c605d8cb0dfd7f53266228461a3a
BLAKE2b-256 0c7eb64d1cd39f6e6a25479b616ed3bdc9d62f9cd7e52555c4c3960498763c8d

See more details on using hashes here.

Provenance

The following attestation bundles were made for beehave-0.4.0.tar.gz:

Publisher: pypi-publish.yml on nullhack/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 beehave-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: beehave-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 17.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for beehave-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c2a0335671a4f865c1fdad1e5e7511297faf0f1d2a93dc14c0ea83461f323616
MD5 9b522ed21ec4d2afc6ad746318e0fb90
BLAKE2b-256 2afab00734b18ff765b412b1021c7eca9bdf38f30eaeac81b0005c675140edf6

See more details on using hashes here.

Provenance

The following attestation bundles were made for beehave-0.4.0-py3-none-any.whl:

Publisher: pypi-publish.yml on nullhack/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