Skillversitas

Step Implementations

Step implementations are the Python functions that execute the text of each Gherkin step. Behave discovers them by scanning modules inside features/steps/ and registering every decorated function. Understanding registration, scoping, and failure semantics turns a bundle of snippets into a maintainable step library.

Registration and Matching

Each decorator binds a function to one or more step patterns. Behave tries the Given, When, Then pools in order and invokes the first match with the highest specificity:

from behave import given, when, then

@given("a cart with a single product")          # literal
def cart_single(context):
    context.cart = ["coffee"]

@given("a cart with {n:d} products")           # parameterized
def cart_multiple(context, n):
    context.cart = ["item-%d" % i for i in range(n)]

@when("a product is removed")                  # plain action
def remove_item(context):
    context.cart.pop()

@then("the cart has {n:d} products")
def has_n_items(context, n):
    assert len(context.cart) == n, "cart=%r" % (context.cart,)

Multiple decorators can point one function at several spellings to avoid near-duplicate code:

@when("the user logs in")
@when("the user signs in")
def log_in(context):
    login(context.user)          # helper defined elsewhere in this module

Where Implementation Logic Belongs

Step functions should be thin: parse arguments, orchestrate page objects or API clients, assert on results. Business logic inside steps is smells a code-review habit worth avoiding:

  • Steps translate Gherkin to code; they are not the place for algorithms.
  • Reuse page objects, API wrappers, or service modules; steps call them.
  • Duplicated 10-line bodies across steps indicate a missing shared helper.
  • Helper functions used only by steps can live in the same module unless they are needed by several step files — put those in a shared package under features/steps/support/ or an importable location.

Failures as Assertions

@then("the response is accepted")
def accepted(context):
    assert context.response.status_code == 202, (
        "expected accepted, got %s: %r" % (
            context.response.status_code, context.response.text))

Behave flags a step as failed when its function raises anything. AssertionError is idiomatic; raising Exception("_not yet implemented") marks a step as "pending" during development. A failing step aborts the scenario — later steps run as skipped models unless the runner is configured otherwise.

Common Exceptions

SituationResult
Decorated function found in features/steps/Registered; steps matching its pattern execute it.
Function defined but no decoratorImported yet never invoked — top suspect for "undefined" steps.
Decorator pattern with a typoEach step in the feature reports undefined; behave prints snippets on --format=plain runs.
Ambiguous patternsError at match time — behave refuses to guess.
Function raisesStep fails; scenario marked as failed with the original traceback shown.

One Module Per Domain

Organize step modules by domain exactly like features: authentication_steps.py, cart_steps.py, payment_steps.py. Keep the module importable with no side effects except decoration. Roughly matching step files to feature files prevents both mega-modules holding hundreds of functions and orphan one-line modules for a single step.

features/steps/
    authentication_steps.py
    cart_steps.py
    payment_steps.py

Summary

Steps are decorated functions bound by pattern, executed in order and failing via raised exceptions. Keep them thin, group them by domain, and remember that an undecorated function is dead code. The next chapters dig into the context parameter those functions all receive.