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. 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.3.0.tar.gz (54.4 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.3.0-py3-none-any.whl (17.1 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for beehave-0.3.0.tar.gz
Algorithm Hash digest
SHA256 e43089d77047904bee4edb74a7b3b8e03b26ad20a5b967778d6e87e2c8f1d258
MD5 a560d251667690de639975b474b5d505
BLAKE2b-256 0be1359f0678a1e1bad90653f1e06c2ad363df166d034ebb707d73009c4e7d10

See more details on using hashes here.

Provenance

The following attestation bundles were made for beehave-0.3.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.3.0-py3-none-any.whl.

File metadata

  • Download URL: beehave-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 17.1 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9bab45e4c5f1a35daca6636f46c82af6bc152f1bdc90122c2faa5c09a27e6315
MD5 cf6fc507b43ab2ac586e5fec0b21ec88
BLAKE2b-256 c0cde49a48e96775df3151fa151e38b9690d162520fd5174ed04770f627de1ca

See more details on using hashes here.

Provenance

The following attestation bundles were made for beehave-0.3.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