Skip to content

Repository files navigation

beehave

PythonLicensePyPI

Keep your living documentation and test code in sync — without step definitions.


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 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:

ConstraintWhy
Titles contain only letters, digits, and spacesThey become Python identifiers (test_...) and file paths
"quoted strings" or 'single-quoted strings' and bare numbers in step text are enforced literalscheck verifies they appear as Constant nodes in the function body (case-insensitive)
<placeholder> names must be valid Python identifiers, not keywords or builtinsThey become function parameters (case-insensitive matching)
Scenario titles are globally unique across all featuresOne function name = one scenario, everywhere

Usage

Write a feature

# docs/features/hive_activity.featureFeature: Hive ActivityBackground:
Given the hive is active
Scenario Outline: honey production from nectarGiven 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 visitorGiven 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 nectarGiven 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.pyfromhypothesisimportgiven, example, strategiesasst@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())deftest_honey_production_from_nectar(nectar, rate, hours, honey):
...
# tests/features/hive_activity/hive_defense_test.pyfromhypothesisimportgiven, strategiesasst@given(scent=st.text(), outcome=st.text())deftest_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())deftest_guard_bee_inspects_visitor(scent, outcome):
assert"floral"inknown_scents()
assert2==guard_count()
assertscentin ("floral", "citrus")
assertoutcomein ("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

Show development status

beehave status # all features with stage and tree output
beehave status --json # machine-readable JSON
beehave status --include-unmapped # include unmapped test directories

Output example:

hive_activity (Hive Activity) needs fixes
├── guard bee inspects visitor 1 error
└── forager returns with nectar ok
comb_construction (Comb Construction) ok
dance_language (Waggle Dance Communication) needs bodies
├── round dance no body
└── waggle dance no body

What check enforces

CheckSeverityWhat it catches
unmapped-scenarioerrorScenario has no matching test function
unmapped-testerrorTest function has no matching scenario
missing-placeholdererror<placeholder> not referenced in function body
missing-literalerror"string" or numeric literal not in function body
example-mismatcherrorExamples row has no matching @example() or vice versa
misplaced-testwarningFunction in wrong file (e.g., after Rule removal)
invalid-feature-titleerrorFeature title fails charset or word count rules
invalid-rule-titleerrorRule title fails charset or word count rules
invalid-scenario-titleerrorScenario title fails charset or word count rules
duplicate-feature-titleerrorDuplicate Feature title (case-insensitive, global)
duplicate-rule-titleerrorDuplicate Rule title or Rule-Feature collision
duplicate-scenario-titleerrorDuplicate Scenario title or Scenario-title collision

beehave check also validates feature, rule, and scenario titles: 2–6 words, [\w\s]+ charset (letters, digits, spaces, underscores), case-insensitive global uniqueness across all title types.


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 'single-quoted strings' and numeric tokens in step text are enforced as Constant AST nodes in the function body. Matching is case-insensitive: "Rex" in Gherkin matches "rex" in test body. Both single and double quote styles are supported. [...] bracket notation inside quotes is preserved verbatim.

Configuration

# pyproject.toml
[tool.beehave]
features_dir = "docs/features"tests_dir = "tests/features"default_strategy = "text"background_check_numeric = truebackground_check_string = true
OptionDefaultDescription
features_dirdocs/featuresWhere .feature files live
tests_dirtests/featuresWhere generated tests go
default_strategytextFallback strategy for unknown placeholders
background_check_numerictrueEnforce numeric literals from Background steps
background_check_stringtrueEnforce string literals from Background steps

License

MIT

About

Beehave does the worker's bee work for you. Living documents and tests in sync every time.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages