In Behave, what does features/environment.py define, and what is the context object?
answer
- One module at the features-directory root
- Hooks are found by their names
- The same object every step receives
- Layers pushed and popped per scenario
- before_all through after_tag
basics
~20 sBehave loads features/environment.py and calls the hook functions it finds there by name: before_all, before_feature, before_scenario, before_step, before_tag and their after_ counterparts. Each is handed the same Context object, layered so scenario-level attributes vanish when the scenario ends.
solid answer
~40 s`features/environment.py` is Behave's hook file. There is no decorator or registration call — Behave looks the functions up **by name** at the root of the features directory, so `before_all(context)`, `before_feature(context, feature)`, `before_scenario(context, scenario)`, `before_step(context, step)`, `before_tag(context, tag)` and the matching `after_*` functions are hooks purely because of what they are called. Every one of them receives `context`, the same object Behave passes as the explicit first argument to every step function in `features/steps`. `context` is a **layered** namespace: Behave pushes a layer around each feature and scenario and pops it afterwards, so an attribute set in `before_scenario` is gone once that scenario ends, while one set in `before_all` survives the run. It also carries run data — `context.table`, `context.text`, `context.feature`, `context.scenario`, `context.failed` and `context.config.userdata`.
code
python · 18 lines# features/environment.py
def before_all(context):
base = context.config.userdata.get("api", "http://localhost:8091")
context.timetable_api = TimetableClient(base) # lives for the whole run
def before_scenario(context, scenario):
context.booked_sailings = [] # dropped when the scenario ends
def after_scenario(context, scenario):
context.timetable_api.reset_bookings()
# features/steps/timetable_steps.py
from behave import given, then
@given("the Harbour Point route has {count:d} sailings")
def step_impl(context, count):
context.sailings = context.timetable_api.seed(count)go deeper
Recall the file name and location — environment.py at the features-directory root — and at least the before_all / before_scenario / after_scenario names. Know that context is the first argument to every step function.
Explain the mechanics: hooks bound by name with no decorator, the full hook set with its signatures, and the layer push and pop that makes a before_scenario attribute disappear at scenario end.
Demonstrate judgement about what belongs in which layer — a client built once in before_all versus per-scenario state — and know fixtures as the way to keep setup and teardown from drifting apart.
Own the convention for a large Python suite: how much structure to hang off an untyped context, when a misspelled attribute becomes a class of defect worth linting for, and how that shapes review standards.
Behave is the Python member of the Gherkin family. It reads the same `.feature` files as the rest of the family, but everything below the feature file is spelled in Python idioms, and two of those idioms carry almost all of the surprise: hooks are found by **file and function name**, and per-scenario state lives on a single object called `context` that is passed explicitly rather than bound implicitly. ## environment.py: hooks by name, not by decoration Behave looks for a module called `environment.py` at the root of the features directory — the same directory that holds the `.feature` files and the `steps` package. It then calls whichever of a fixed set of function names it finds. Nothing marks them; a typo in the name produces a hook that silently never runs, which is the single most common Behave debugging story. | Hook | Signature | Runs | | --- | --- | --- | | `before_all` / `after_all` | `(context)` | once per run | | `before_feature` / `after_feature` | `(context, feature)` | around each feature | | `before_scenario` / `after_scenario` | `(context, scenario)` | around each scenario | | `before_step` / `after_step` | `(context, step)` | around each step | | `before_tag` / `after_tag` | `(context, tag)` | around each tagged element | Two of those need care. A `Scenario Outline` expands into one runnable scenario per `Examples` row before hooks run, so `before_scenario` fires once **per row**, not once per outline — the same trap that makes people put expensive setup in the wrong place in every implementation. And `before_tag` is not a filter: it fires for each tag on the element being entered, so conditional setup is written as a check on the tag value rather than as a tagged hook attribute the way the JVM and .NET implementations write it. For paired setup and teardown Behave also offers **fixtures**: a generator function decorated with `@fixture` that yields once, activated with `use_fixture(the_fixture, context, ...)` from inside a hook. That gives you guaranteed cleanup without hand-writing the matching `after_` half, and it is the idiomatic way to express something like "a seeded timetable, torn down afterwards". ## What context actually is `context` is a run-scoped namespace object, and Behave passes the **same** object to every hook and, as the first positional argument, to every step function decorated with `@given`, `@when`, `@then` or `@step`. You attach whatever you like to it with plain attribute assignment. What makes it more than a bag is that it is **layered**. Behave pushes a new layer before entering a feature and another before entering a scenario, and pops each layer on the way out. Anything assigned inside a layer disappears when that layer is popped, and a name assigned in an inner layer temporarily shadows an outer one rather than destroying it. That gives you scoping for free: - Set a shared HTTP client in `before_all` and it is visible to every step of the run. - Set `context.booked_sailings = []` in `before_scenario` and the next scenario starts without it, so leakage between scenarios is the exception rather than the default. - Assign in a step and it lives for the rest of that scenario, which is how Behave answers the "pass state between steps" problem. Behave also populates `context` with run data. `context.table` holds a step's data-table argument and `context.text` its doc string — both are `None` when the step has neither, which is why steps read them defensively. `context.feature` and `context.scenario` expose the model objects for what is currently running, `context.failed` reports whether the run has failed so far, and `context.config.userdata` carries values passed on the command line with `-D name=value`, the standard channel for environment selection. `context.execute_steps` lets a step invoke other Gherkin steps as text, which is powerful and easy to abuse. ## How this maps onto the rest of the family `context` occupies the same slot as cucumber-js's World and as the per-scenario objects SpecFlow and Reqnroll inject into binding-class constructors — but the ergonomics differ in ways worth naming out loud. It is **explicit**: it arrives as an argument rather than as an implicit `this`, so there is no arrow-function trap and no type declaration to remember. It is **untyped**: attributes are created by assignment, so a misspelling is an `AttributeError` at run time rather than a compile error, and static analysis cannot help you. And it is **one object**, not a set of injected types, so a large suite has to impose its own structure — usually a small number of namespaced objects hung off `context` — where a .NET suite gets that structure from the container. On a ferry-timetable booking product with a 63-scenario feature set, the layering is what keeps the eleven-minute pipeline honest: one API client built in `before_all`, per-scenario booking state created in `before_scenario`, and nothing global to reset between scenarios because the layer pop already did it.
- A Behave hook you added to environment.py never runs. What do you check first?The function name and the file location. Behave binds hooks purely by name at the root of the features directory, so `before_scenerio`, a hook defined in `features/steps/`, or a second `environment.py` shadowing the first all produce a hook that is simply never called — with no error. Confirm the spelling against the fixed hook names and confirm which directory Behave is treating as the features root.
- How does Behave's context differ from cucumber-js's World in day-to-day use?`context` is passed explicitly as the first argument to every step and hook, so there is no `this` binding to lose and no arrow-function trap. It is also one untyped object with layered scoping rather than a class you define, so misspelled attributes fail at run time and structure has to be imposed by convention instead of by types.
- What is the difference between using an after_scenario hook and using a fixture in Behave?An `after_scenario` hook runs for every scenario and you write the setup and teardown halves separately, so they can drift. A `@fixture` is a generator that yields once, activated with `use_fixture`, keeping setup and cleanup in one function and guaranteeing the cleanup runs. Fixtures also compose: a hook can activate different fixtures depending on the scenario's tags.
saying these in an interview costs you the question
- Thinks Behave hooks need a decorator or explicit registration
- Puts environment.py inside the steps package and wonders why nothing runs
- Assumes attributes set in before_scenario persist for the whole run
- Treats before_tag as a filter that decides whether a scenario runs
- Expects before_scenario to fire once per Scenario Outline rather than per Examples row