Feature Files
Feature files are where business requirements live as executable text. A well-crafted feature file reads like a spec sheet a stakeholder can review without a Python window open. This chapter covers anatomy beyond the basics: feature-level structure, tables, docstrings, naming, and organization practices.
Anatomy of a Feature File
Feature: Shopping cart totals
As a shopper
I want the cart to sum items correctly
So that I am charged the right amount
Background:
Given the catalog has "coffee" at 4.50 USD
Scenario: Single item added
Given an empty cart
When "coffee" is added to the cart
Then the cart total is 4.50 USD
Scenario: Two line items
Given an empty cart
When "coffee" is added to the cart
And 2 units of "coffee" are added
Then the cart total is 13.50 USD
Scenario: Discount applies (outline)
Given an empty cart
When a code <code> is applied to a cart with <items> items
Then the cart total is <total> USD
Examples:
| code | items | total |
| SAVE10 | 2 | 8.10 |
Each part has a role: the feature header carries user-story context; the Background preloads shared state; concrete scenarios cover fixed cases; the outline with Examples sweeps a parameter grid.
Tables Inside Steps
Any step can be followed by a pipe-delimited table that behaves attaches to it as context.table. First row is the header:
Scenario: Many products at once
Given the following products exist:
| name | price | stock |
| coffee | 4.50 | 100 |
| tea | 3.20 | 50 |
| cocoa | 5.00 | 0 |
@given('the following products exist')
def step_products(context):
for row in context.table:
add_product(row["name"], float(row["price"]), int(row["stock"]))
Docstrings Inside Steps
For long text payloads — JSON, SQL, prose — use a docstring delimited by three backticks or three double quotes. It arrives as plain text in context.text:
When the API receives the payment:
"""
{
"order_id": 1729,
"method": "card",
"amount": 41.70
}
"""
@when("the API receives the payment")
def step_payment(context):
body = context.text
response = post_payment(json.loads(body))
assert response.ok
What's Not Allowed
Gherkin stays deliberately small. In feature files you cannot:
- Define variables, loops, or conditionals — that is what scenario outlines and Python steps are for.
- Nest scenarios or reference another file's scenarios; each scenario stands alone.
- Compute in step text; values come from tables, outlines, or hidden fixture data.
- Call Python directly; the only bridge is registered step implementations.
File Organization Practices
- One capability per file keeps diffs reviewable and
--tagsruns efficient. - Put the most important scenarios first — they run first and fail fast in CI.
- Keep step text free of implementation noise ("click button three times", not "press FORM_NEXT twice").
- Version feature files with the code they test; they belong in the same repository so a pull request can change code and spec together.
Summary
A feature file combines a header, optional background, concrete scenarios, and outlines with example tables, plus tables and docstrings attached to individual steps. Its vocabulary is intentionally tiny: rich behavior belongs in Python, while the file keeps a single readable narrative per capability.