Distributed BDD testing with Python Behave via remote step providers
Project description
Remote Behave Steps
Distributed BDD fixture setup for Python Behave.
The problem: In distributed systems, BDD scenarios need preconditions like "Given I have 3 existing to-do items" -- but the test runner can't efficiently manipulate databases, queues, and caches that live inside other services. You end up writing fragile setup code that reaches across service boundaries, or maintaining a parallel set of test fixtures that drift from production behavior.
The solution: Services in your system expose HTTP endpoints that know how to set up their own state. Remote Behave Steps discovers these endpoints from standard OpenAPI specs and registers them as native Behave steps. Your feature files look exactly the same -- the library handles discovery, invocation, and context passing transparently.
Feature: To-Do Management
Scenario: Create and verify to-do items
Given I have "3" existing to-do items # <-- served by the to-do service
And a to-do item titled "Buy milk" # <-- also remote
When I request the to-do list # <-- local step, hits the public API
Then I should see 4 items # <-- local assertion
The Given steps above are defined in the to-do service's OpenAPI spec, discovered at test time, and invoked over HTTP. The test author doesn't need to know where the steps live.
How It Works
- A service adds an OpenAPI spec with
x-behave-patternon its fixture endpoints - The test project points
remote_behave_stepsat the service's spec URL - At test time, the library fetches the spec, registers each pattern as a Behave step, and invokes endpoints via HTTP when those steps execute
Feature File remote_behave_steps Service
─────────── ─────────────────── ───────
Given I have "3" ──> matches pattern, ──> PUT /existing-todos
existing to-do builds request with { context: {...},
items context + inputs inputs: {count: "3"} }
<── returns data to context <── { status: "ok",
data: {created_ids: [1,2,3]} }
Installation
pip install remote-behave-steps
Or with uv:
uv add remote-behave-steps
Requirements
- Python 3.10+
- Behave 1.2.6+
Quick Start
1. Configure your server
Add the remote service's OpenAPI spec URL to your pyproject.toml:
[tool.remote_behave_steps]
cache_ttl = 300 # seconds; set to 0 during local development
[[tool.remote_behave_steps.servers]]
name = "todo-service"
url = "http://localhost:8080/openapi.yaml"
2. Register remote steps
Create a step file that imports the auto-registration module. This is the only code you need:
# features/steps/remote.py
import remote_behave_steps.auto # noqa: F401
That's it. All steps discovered from the configured servers are now available in your feature files.
3. Write your feature
Feature: To-Do Management
Scenario: Create generic to-do items
Given I have "3" existing to-do items
When I request the to-do list
Then I should see 3 items
The Given step is served remotely. Write your When and Then steps locally as usual.
4. Run behave
behave
Alternative Registration Styles
The auto-import is the simplest approach, but you can also register explicitly:
# features/steps/remote.py
# Option A: Explicit call, config from pyproject.toml
from remote_behave_steps import register_remote_steps
register_remote_steps()
# Option B: Fully programmatic (dynamic URLs, CI environments, etc.)
from remote_behave_steps import register_remote_steps
register_remote_steps(servers=["http://localhost:8080/openapi.yaml"])
# Option C: Multiple servers with options
from remote_behave_steps import register_remote_steps
register_remote_steps(servers=[
{"name": "todo-service", "url": "http://todo:8080/openapi.yaml"},
{"name": "auth-service", "url": "http://auth:8080/openapi.yaml", "timeout": 5000},
])
Writing a Remote Step Provider
A remote step provider is any HTTP service that serves an OpenAPI spec with x-behave-pattern extensions on its fixture endpoints. The service can be written in any language or framework.
Minimal example (FastAPI)
FastAPI's openapi_extra parameter lets you declare the step metadata right on the endpoint decorator -- no separate config or post-processing needed:
from fastapi import FastAPI, Request
app = FastAPI(title="My Fixture Service", version="1.0.0")
@app.put("/existing-todos", summary="Create N generic to-do items", openapi_extra={
"x-behave-pattern": 'I have "{count}" existing to-do items',
"x-behave-step-type": "given",
})
async def existing_todos(request: Request):
body = await request.json()
count = int(body["inputs"]["count"])
# ... create the to-do items in your database ...
return {"status": "ok", "data": {"created_ids": [1, 2, 3]}}
The openapi_extra dict is merged into the operation in the generated OpenAPI spec, so the library discovers the step automatically.
OpenAPI extensions
If you're not using FastAPI, add these extensions to your OpenAPI spec manually:
paths:
/existing-todos:
put:
summary: Create N generic to-do items
x-behave-pattern: 'I have "{count}" existing to-do items'
x-behave-step-type: given # optional, defaults to "given"
x-behave-timeout: 10000 # optional, per-step timeout in ms
Step types
Each step declares its type with x-behave-step-type. Valid values are given, when, then, and step (registers for all types). The default is given, reflecting the library's primary purpose: remote fixture setup.
While you can implement any step type remotely, the recommended pattern is:
- Remote services handle Given steps (fixture setup, state manipulation)
- Test runner handles When and Then steps (actions and assertions against public interfaces)
Request format
Every step endpoint receives a PUT request with this body:
{
"context": {
"run_id": "550e8400-e29b-41d4-a716-446655440000",
"feature": {"name": "To-Do Management", "file": "features/todo.feature", "tags": []},
"scenario": {"name": "Create items", "id": "...", "tags": []}
},
"inputs": {
"count": "3",
"table": {"headings": ["title", "priority"], "rows": [["Buy milk", "high"]]},
"text": "multiline docstring content"
}
}
contextis auto-populated by the library (run ID, feature/scenario metadata)inputscontains the captured pattern parameters, plus optionaltableandtextfrom Gherkin
Response format
{
"status": "ok",
"data": {"created_ids": [1, 2, 3]}
}
Any values in data are merged into context.remote_data on the Behave side, making them available to subsequent steps.
Standard endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/healthz |
GET | Liveness probe. Returns 200 when ready. |
/reset-all-data |
PUT | Reset to clean baseline. Called at the start of each test run. |
/openapi.yaml |
GET | The OpenAPI spec with x-behave-pattern extensions. |
Lifecycle Hooks
Remote services can optionally participate in Behave's lifecycle by implementing well-known hook endpoints:
| Path | Behave Hook | When Called |
|---|---|---|
/hooks/before-all |
before_all |
Start of test run (unconditional) |
/hooks/after-all |
after_all |
End of test run (unconditional) |
/hooks/before-feature |
before_feature |
Before each @remote_hooks feature |
/hooks/after-feature |
after_feature |
After each @remote_hooks feature |
/hooks/before-scenario |
before_scenario |
Before each @remote_hooks scenario |
/hooks/after-scenario |
after_scenario |
After each @remote_hooks scenario |
/hooks/before-step |
before_step |
Before each step in @remote_hooks scope |
/hooks/after-step |
after_step |
After each step in @remote_hooks scope |
Hooks require the @remote_hooks tag on the feature or scenario (except before_all/after_all). Wire them up in environment.py:
# features/environment.py
from remote_behave_steps import hooks
def before_scenario(context, scenario):
hooks.before_scenario(context, scenario)
def after_scenario(context, scenario):
hooks.after_scenario(context, scenario)
All hook endpoints are optional. The library discovers which hooks a service supports from its OpenAPI spec.
Configuration Reference
pyproject.toml
[tool.remote_behave_steps]
cache_ttl = 300 # How long to cache discovered specs (seconds). Default: 300
[[tool.remote_behave_steps.servers]]
name = "todo-service" # Optional display name (derived from URL if omitted)
url = "http://localhost:8080/openapi.yaml" # Required
timeout = 30000 # Default step timeout in ms. Default: 30000
Full Documentation
For the complete API meta-specification, request/response schemas, lifecycle hook details, and example OpenAPI specs, see the project repository.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file remote_behave_steps-0.2.0.tar.gz.
File metadata
- Download URL: remote_behave_steps-0.2.0.tar.gz
- Upload date:
- Size: 21.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
507afe0671f55b891c2783be6c44a4694d50cc731889ae214d46f02eced24ad9
|
|
| MD5 |
2abf640a268328aaf01a9bf0501581cf
|
|
| BLAKE2b-256 |
77e906c9ebf1c6dd382e8f69c270868be3b94226d9eb71e0bdb451397c3e393b
|
Provenance
The following attestation bundles were made for remote_behave_steps-0.2.0.tar.gz:
Publisher:
publish.yml on ctrahey/remote-behave-steps
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
remote_behave_steps-0.2.0.tar.gz -
Subject digest:
507afe0671f55b891c2783be6c44a4694d50cc731889ae214d46f02eced24ad9 - Sigstore transparency entry: 1060247786
- Sigstore integration time:
-
Permalink:
ctrahey/remote-behave-steps@3054ce5107f16f616c8b28a1fadaddaa98588075 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ctrahey
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3054ce5107f16f616c8b28a1fadaddaa98588075 -
Trigger Event:
push
-
Statement type:
File details
Details for the file remote_behave_steps-0.2.0-py3-none-any.whl.
File metadata
- Download URL: remote_behave_steps-0.2.0-py3-none-any.whl
- Upload date:
- Size: 14.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3cbcadcff12bac1b7bdb372db78fe3a36f16814e0b6b80147432bd01ea38c2a
|
|
| MD5 |
b9d0f03e0a3c325d596f0a872178295a
|
|
| BLAKE2b-256 |
85d9c6c10e38502e7d692a46534aa88c04e948ae88992e12934209d397c16f73
|
Provenance
The following attestation bundles were made for remote_behave_steps-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on ctrahey/remote-behave-steps
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
remote_behave_steps-0.2.0-py3-none-any.whl -
Subject digest:
a3cbcadcff12bac1b7bdb372db78fe3a36f16814e0b6b80147432bd01ea38c2a - Sigstore transparency entry: 1060247826
- Sigstore integration time:
-
Permalink:
ctrahey/remote-behave-steps@3054ce5107f16f616c8b28a1fadaddaa98588075 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ctrahey
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3054ce5107f16f616c8b28a1fadaddaa98588075 -
Trigger Event:
push
-
Statement type: