Skillversitas

Feature Testing Setup

Before writing many features, decide where files live, how they are discovered, and how the test code separates from application code. A deliberate layout prevents import problems, duplicated hooks, and CI friction later. This chapter establishes the standard behave project skeleton and the choices you should make once.

Standard Directory Structure

project/
    behave.ini
    features/
        environment.py
        steps/
            authentication_steps.py
            api_steps.py
        features1.feature
        features2.feature
    src/                  # application code under test
        myapp/
            __init__.py
            service.py
    tests/                # non-BDD unit tests, unrelated to behave

Key rules: step modules belong in features/steps/ so behave imports them automatically; environment.py must sit directly in the feature directory; helper modules shared by steps can live in features/steps/ or anywhere importable. If application code lives in src/, add that path to sys.path in environment.py or install the package in editable mode (pip install -e .).

Naming and Partitioning Features

  • One .feature file per capability, e.g., account_password_reset.feature.
  • Avoid catch-all files like all_tests.feature — they grow unmanageably.
  • Import subdirectories under features/ are also scanned; both file and directory paths work (features/my/entrance/myfile.feature or the whole tree at once).
  • Keep step files themed like feature files so reviewers can locate implementations fast: all login steps in one module, all HTTP steps in another.

Minimal First Feature Set

Seed the setup with one feature and one steps module to prove paths and imports resolve:

Feature: Service health

  Scenario: Endpoint answers
    Given the service is running
    When the health endpoint is polled
    Then the status is OK
from behave import given, when, then

@given("the service is running")
def step_running(context):
    context.base_url = "http://localhost:8080"

@when("the health endpoint is polled")
def step_poll(context):
    import requests
    context.response = requests.get(context.base_url + "/health")

@then("the status is OK")
def step_status(context):
    assert context.response.status_code == 200

Configuration Side: behave.ini

Place behave.ini in the directory from which you run (project root is typical) so teammates and CI need no per-machine flags:

[behave]
paths = features
steps_dir = steps
stdout_capture = yes
stderr_capture = yes
format = pretty
default_tags = ~@skip

Command-line arguments always override behave.ini, so the file encodes common defaults, not absolutes.

Environment Side: environment.py

Use environment.py for cross-scenario infrastructure — clients, database connections, test data factories — not for scenario logic:

# features/environment.py
import sys, os

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

def before_all(context):
    context.config.userdata.get("env", "staging")   # -D env=... on the CLI

def before_scenario(context, scenario):
    context.base_url = "http://localhost:8080"

def after_scenario(context, scenario):
    # cleanup that must run even on failure goes here
    pass

Running From the Right Directory

Behave resolves features relative to the current working directory. Run it from the project root in local shells, IDE launch configurations, and CI pipelines; otherwise paths in behave.ini silently miss the feature tree and behave exits with "no feature files found".

Summary

A sound setup is a features/ tree with a steps/ subfolder, an environment.py for hooks and imports, and a behave.ini for shared defaults. Keep application imports resolveable and always launch behave from the project root — these two decisions cause most new-team setup pain.