How to Use Cucumber for Python (Behave)
Cucumber is a powerful tool for Behavior-Driven Development (BDD), enabling teams to write tests in a natural, human-readable format. In Python, the most popular implementation of Cucumber is Behave. Using Gherkin syntax and the Given-When-Then style, Behave helps QA engineers, developers, and product teams collaborate effectively while keeping tests clean, readable, and scalable.
1. Installing and Setting Up Behave
pip install behave
Create the recommended project structure:
project/
└── features/
├── steps/
├── environment.py
└── example.feature
2. Writing Your First Feature File
# example.feature
Feature: Basic math operations
As a user
I want to perform simple math
So that I can see correct results
Scenario: Adding two numbers
Given I have two numbers 3 and 5
When I add them
Then the result should be 8
3. Implementing Step Definitions
# steps/math_steps.py
from behave import given, when, then
@given("I have two numbers 3 and 5")
def step_given_two_numbers(context):
context.a = 3
context.b = 5
@when("I add them")
def step_when_add(context):
context.result = context.a + context.b
@then("the result should be 8")
def step_then_result(context):
assert context.result == 8
4. Scenario Outlines with Examples
Scenario Outline: Multiplying numbers
Given I have numbers <a> and <b>
When I multiply them
Then the result should be <result>
Examples:
| a | b | result |
| 2 | 3 | 6 |
| 4 | 5 | 20 |
@given('I have numbers {a:d} and {b:d}')
def step_given_numbers(context, a, b):
context.a = a
context.b = b
@when("I multiply them")
def step_multiply(context):
context.result = context.a * context.b
@then("the result should be {result:d}")
def step_then_check(context, result):
assert context.result == result
5. Hooks and Environment Setup
# environment.py
def before_all(context):
print("Test suite is starting...")
def before_scenario(context, scenario):
print(f"Starting scenario: {scenario.name}")
def after_scenario(context, scenario):
print(f"Finished scenario: {scenario.name}")
def after_all(context):
print("All tests completed.")
6. Using Background for Shared Setup
The Background section allows you to define steps that run before each scenario in a feature. It is ideal for repetitive setup like opening pages, setting initial states, or preparing test data.
# example.feature
Feature: User login
Background:
Given I am on the login page
Scenario: Successful login
When I enter valid credentials
Then I should see the dashboard
Scenario: Failed login
When I enter invalid credentials
Then I should see an error message
@given("I am on the login page")
def step_open_login(context):
context.page = "login"
@when("I enter valid credentials")
def step_valid_credentials(context):
context.result = "dashboard"
@when("I enter invalid credentials")
def step_invalid_credentials(context):
context.result = "error"
@then("I should see the dashboard")
def step_dashboard(context):
assert context.result == "dashboard"
@then("I should see an error message")
def step_error(context):
assert context.result == "error"
7. Custom Parameter Types
Behave allows you to define custom parameter converters. This improves readability and eliminates manual parsing inside step functions.
# steps/custom_types.py
from behave import register_type
import parse
def parse_float(text):
return float(text)
register_type(Float=parse_float)
Usage inside a step:
@when("I divide {a:Float} by {b:Float}")
def step_divide(context, a, b):
context.result = a / b
8. Reusing Steps Across Features
Behave loads all step definitions from the steps/ directory, which makes it easy to create shared step libraries. This prevents duplication and keeps tests maintainable.
features/
└── steps/
├── common_steps.py
├── login_steps.py
├── api_steps.py
└── math_steps.py
# steps/common_steps.py
from behave import given
@given("the system is initialized")
def step_system_initialized(context):
context.initialized = True
Scenario: System check
Given the system is initialized
When I run diagnostics
Then it should succeed
9. Working with Data Tables
Data tables provide a clean way to pass multiple rows of structured data into steps. This is ideal for lists of users, test datasets, or bulk actions.
Scenario: Creating multiple users
Given the following users exist:
| name | role |
| John | admin |
| Alice | editor |
| Bob | viewer |
@given("the following users exist:")
def step_users_exist(context):
context.users = []
for row in context.table:
context.users.append({
"name": row["name"],
"role": row["role"]
})
10. Running Behave Tests
behave
behave features/example.feature
behave --tags=@smoke
11. Best Practices
- Write feature files in business language.
- Keep step definitions short and reusable.
- Put complex logic inside helper modules.
- Use Background only when absolutely needed.
- Organize steps into logical modules.
- Use custom types to avoid manual parsing.
