Integration examples¶
This page focuses on copy-paste patterns for real Behave projects. Start with the smallest recipe that solves your problem, then add the optional layers only when they earn their place.
Recipe 1: the smallest useful integration¶
Use this when you only want scoped object creation and cleanup.
features/
behave-toolkit.yaml
environment.py
steps/
reporting_steps.py
smoke.feature
features/behave-toolkit.yaml:
version: 1
variables:
report_name: report.json
objects:
workspace:
factory: tempfile.TemporaryDirectory
scope: feature
cleanup: cleanup
workspace_path:
factory: pathlib.Path
scope: feature
args:
- $ref: workspace
attr: name
report_path:
factory: pathlib.Path
scope: scenario
args:
- $ref: workspace_path
- $var: report_name
features/environment.py:
from pathlib import Path
from behave_toolkit import (
activate_feature_scope,
activate_scenario_scope,
install,
)
CONFIG_PATH = Path(__file__).with_name("behave-toolkit.yaml")
def before_all(context):
install(context, CONFIG_PATH)
def before_feature(context, feature):
del feature
activate_feature_scope(context)
def before_scenario(context, scenario):
del scenario
activate_scenario_scope(context)
features/steps/reporting_steps.py:
from behave import then
@then("the report file can be written")
def step_report_file_can_be_written(context):
context.report_path.write_text("ok\n", encoding="utf-8")
assert context.report_path.exists()
Recipe 2: split config into a directory¶
Use this when one YAML file starts to feel crowded or when different people own different areas.
features/
behave-toolkit/
00-variables.yaml
10-parsers.yaml
20-objects.yaml
environment.py
support_types.py
features/behave-toolkit/00-variables.yaml:
version: 1
variables:
environment_name: qa
base_url: https://qa.example.test
report_name: latest-report.json
features/behave-toolkit/10-parsers.yaml:
parsers:
step_matcher: cfparse
types:
Status:
enum: support_types.Status
case_sensitive: false
features/behave-toolkit/20-objects.yaml:
objects:
artifacts_path:
factory: pathlib.Path
scope: global
args:
- artifacts
test_log_path:
factory: pathlib.Path
scope: global
args:
- $ref: artifacts_path
- test-run.log
workspace:
factory: tempfile.TemporaryDirectory
scope: feature
cleanup: cleanup
workspace_path:
factory: pathlib.Path
scope: feature
args:
- $ref: workspace
attr: name
report_path:
factory: pathlib.Path
scope: scenario
args:
- $ref: workspace_path
- $var: report_name
features/support_types.py:
from enum import Enum
class Status(Enum):
ACTIVE = "active"
PENDING = "pending"
Recipe 3: one environment.py that combines the common optional helpers¶
Use this when you want parser helpers, feature-file variables, scenario cycling, and one persistent log together.
from pathlib import Path
from behave_toolkit import (
activate_feature_scope,
activate_scenario_scope,
configure_parsers,
configure_test_logging,
expand_scenario_cycles,
format_cycle_progress,
install,
substitute_feature_variables,
)
CONFIG_PATH = Path(__file__).with_name("behave-toolkit")
# Import-time setup for Behave custom types.
configure_parsers(CONFIG_PATH)
def before_all(context):
install(context, CONFIG_PATH)
substitute_feature_variables(context)
added = expand_scenario_cycles(context)
context.test_logger = configure_test_logging(context.test_log_path)
if added:
context.test_logger.info("Expanded %s extra cycle runs", added)
def before_feature(context, feature):
del feature
activate_feature_scope(context)
def before_scenario(context, scenario):
activate_scenario_scope(context)
progress = format_cycle_progress(scenario)
if progress is None:
context.test_logger.info("%s", scenario.name)
else:
context.test_logger.info("Cycle %s -> %s", progress, scenario.name)
The ordering above is the recommended one when you combine these helpers:
configure_parsers(CONFIG_PATH)at import timeinstall(context, CONFIG_PATH)inbefore_all()substitute_feature_variables(context)if you use{{var:name}}expand_scenario_cycles(context)if you use@cycling(N)configure_test_logging(...)orconfigure_loggers(context)after global objects exist
Recipe 4: reuse root variables directly in feature files¶
features/login.feature:
Feature: Login against {{var:environment_name}}
Scenario: Open the configured home page
Given I open {{var:base_url}}
features/environment.py:
from pathlib import Path
from behave_toolkit import install, substitute_feature_variables
CONFIG_PATH = Path(__file__).with_name("behave-toolkit")
def before_all(context):
install(context, CONFIG_PATH)
substitute_feature_variables(context)
Feature placeholders use {{var:name}}. They are resolved from the root variables: section and apply to names, descriptions, step text, docstrings, and tables. Tags are intentionally left unchanged.
Recipe 5: upgrade from one log file to named loggers in YAML¶
Use this when one persistent test-run.log is no longer enough.
Add paths in objects::
objects:
artifacts_path:
factory: pathlib.Path
scope: global
args:
- artifacts
test_log_path:
factory: pathlib.Path
scope: global
args:
- $ref: artifacts_path
- test-run.log
diagnostics_log_path:
factory: pathlib.Path
scope: global
args:
- $ref: artifacts_path
- diagnostics.log
Add named logger definitions:
logging:
test_run:
path:
$ref: test_log_path
logger_name: suite-tests
inject_as: test_logger
diagnostics:
path:
$ref: diagnostics_log_path
logger_name: suite-diagnostics
inject_as: diagnostics_logger
console: false
Wire them from before_all():
from pathlib import Path
from behave_toolkit import configure_loggers, install
CONFIG_PATH = Path(__file__).with_name("behave-toolkit")
def before_all(context):
install(context, CONFIG_PATH)
configure_loggers(context)
Use YAML-defined loggers only when you really want multiple named outputs. For many suites, the smaller configure_test_logging() helper stays the better default.
Recipe 6: generate a step reference site for a downstream suite¶
Once your step library grows, generate a searchable reference site directly from your Behave project:
behave-toolkit-docs --features-dir features --output-dir docs/behave-toolkit --config-path features/behave-toolkit
python -m sphinx -b html docs/behave-toolkit docs/_build/behave-toolkit
Pass --config-path when your feature files use {{var:name}}, so example matching sees the substituted text.
Which recipe should you start with?¶
Start with Recipe 1 if you only need scoped objects and cleanup.
Add Recipe 2 when config ownership grows and one YAML file stops scaling.
Use Recipe 3 if your suite already needs several optional helpers together.
Use Recipe 4 only when feature-file placeholders make scenarios easier to read or easier to share across environments.
Use Recipe 5 only when one persistent suite log is no longer enough.
Use Recipe 6 once your step library has become large enough that discovery is a real problem.