Skillversitas

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 --tags runs 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.