Skip to main content

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

  1. A service adds an OpenAPI spec with x-behave-pattern on its fixture endpoints
  2. The test project points remote_behave_steps at the service's spec URL
  3. 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

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"
  }
}
  • context is auto-populated by the library (run ID, feature/scenario metadata)
  • inputs contains the captured pattern parameters, plus optional table and text from 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

remote_behave_steps-0.2.0.tar.gz (21.0 kB view details)

Uploaded Source

Built Distribution

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

remote_behave_steps-0.2.0-py3-none-any.whl (14.2 kB view details)

Uploaded Python 3

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

Hashes for remote_behave_steps-0.2.0.tar.gz
Algorithm Hash digest
SHA256 507afe0671f55b891c2783be6c44a4694d50cc731889ae214d46f02eced24ad9
MD5 2abf640a268328aaf01a9bf0501581cf
BLAKE2b-256 77e906c9ebf1c6dd382e8f69c270868be3b94226d9eb71e0bdb451397c3e393b

See more details on using hashes here.

Provenance

The following attestation bundles were made for remote_behave_steps-0.2.0.tar.gz:

Publisher: publish.yml on ctrahey/remote-behave-steps

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file remote_behave_steps-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for remote_behave_steps-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a3cbcadcff12bac1b7bdb372db78fe3a36f16814e0b6b80147432bd01ea38c2a
MD5 b9d0f03e0a3c325d596f0a872178295a
BLAKE2b-256 85d9c6c10e38502e7d692a46534aa88c04e948ae88992e12934209d397c16f73

See more details on using hashes here.

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

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