py4u guide

Behavior-Driven Development in Python using Behave

In the world of software development, ensuring that a product meets business requirements while remaining technically sound is a constant challenge. Enter **Behavior-Driven Development (BDD)**, an agile methodology that bridges the gap between technical and non-technical stakeholders by using plain-language descriptions of software behavior. BDD promotes collaboration between developers, testers, product owners, and even end-users, ensuring everyone aligns on "what" the software should do before diving into "how" to build it. For Python developers, **Behave** is a powerful, open-source framework that implements BDD. It uses Gherkin—a human-readable language—to define test scenarios, making it easy for non-technical stakeholders to contribute to and understand test cases. In this blog, we’ll explore BDD fundamentals, dive into Behave, and walk through practical examples to help you implement BDD in your Python projects.

Table of Contents

  1. What is Behavior-Driven Development (BDD)?
  2. Core BDD Concepts
    • Gherkin: The Language of BDD
    • Features, Scenarios, and Steps
  3. Getting Started with Behave
    • Installation
    • Project Structure
  4. Writing Your First Feature File
  5. Implementing Step Definitions
  6. Running Tests and Interpreting Results
  7. Advanced Behave Features
    • Background
    • Scenario Outlines (Data-Driven Testing)
    • Tags
    • Hooks
  8. Best Practices for BDD with Behave
  9. Conclusion
  10. References

1. What is Behavior-Driven Development (BDD)?

BDD evolved from Test-Driven Development (TDD) but shifts the focus from “testing” to “behavior.” It encourages teams to define expected behavior of software through conversations between technical (developers, testers) and non-technical (product owners, users) stakeholders. These conversations result in “living documentation”—testable scenarios that double as requirements.

Key goals of BDD:

  • Align development with business goals.
  • Improve communication and collaboration.
  • Create maintainable, readable tests that serve as documentation.
  • Validate software behavior from the user’s perspective.

2. Core BDD Concepts

Gherkin: The Language of BDD

Gherkin is a plain-text language used to write BDD scenarios. It’s human-readable, making it accessible to non-technical stakeholders, and structured, allowing tools like Behave to automate the scenarios. Gherkin uses keywords (in English or 70+ languages) to define behavior.

Common Gherkin Keywords:

  • Feature: A high-level description of a software feature (e.g., “User Authentication”).
  • Scenario: A specific test case for the feature (e.g., “Successful login with valid credentials”).
  • Given: Sets up the initial context (e.g., “a user with username ‘test’ and password ‘pass’”).
  • When: Describes the action taken (e.g., “the user enters their username and password”).
  • Then: Defines the expected outcome (e.g., “the user is redirected to the dashboard”).
  • And/But: Extends Given, When, or Then for readability (e.g., “And clicks ‘Login’”).
  • Scenario Outline: Reuses a scenario with multiple data sets (data-driven testing).
  • Examples: Provides data for a Scenario Outline (in a table format).

Features, Scenarios, and Steps

  • Feature File: A .feature file containing one or more scenarios for a feature. Stored in a features/ directory (Behave’s default).
  • Scenario: A sequence of Given-When-Then steps that describe a specific behavior.
  • Step: A single action or assertion in a scenario (e.g., Given I have 5 apples).

3. Getting Started with Behave

Installation

Behave is a Python package. Install it via pip:

pip install behave  

Project Structure

Behave follows a standard directory structure. For a project named my_project, organize files like this:

my_project/  
├── features/                # Root directory for BDD tests  
│   ├── calculator.feature   # Feature file(s)  
│   └── steps/               # Step definitions directory  
│       └── calculator_steps.py  # Python code mapping Gherkin steps to logic  
└── behave.ini               # (Optional) Configuration for Behave  
  • features/: Stores all .feature files.
  • features/steps/: Stores Python files with step definitions (matching Gherkin steps to code).

4. Writing Your First Feature File

Let’s create a simple feature for a calculator app: adding two numbers.

Create features/calculator.feature:

Feature: Add Two Numbers  
  As a user  
  I want to add two numbers  
  So that I can get their sum  

  Scenario: Add two positive numbers  
    Given I have entered 5 into the calculator  
    And I have entered 3 into the calculator  
    When I press add  
    Then the result should be 8 on the screen  

Breakdown:

  • Feature: Describes the purpose of the feature (who, what, why).
  • Scenario: Tests adding two positive numbers.
  • Steps: Given initializes the calculator with 5 and 3, When triggers addition, Then checks the result.

5. Implementing Step Definitions

Step definitions are Python functions that map Gherkin steps to executable code. They use Behave’s decorators (@given, @when, @then) to link to Gherkin keywords.

Writing Step Definitions

Create features/steps/calculator_steps.py:

from behave import given, when, then  
from calculator import Calculator  # Assume we have a Calculator class  

@given("I have entered {number:d} into the calculator")  
def step_impl(context, number):  
    # Initialize calculator if not exists in context  
    if not hasattr(context, "calculator"):  
        context.calculator = Calculator()  
    context.calculator.enter_number(number)  

@when("I press add")  
def step_impl(context):  
    context.result = context.calculator.add()  

@then("the result should be {expected:d} on the screen")  
def step_impl(context, expected):  
    assert context.result == expected, f"Expected {expected}, got {context.result}"  

Key Notes:

  • context: A special object passed between steps to share data (e.g., context.calculator, context.result).
  • {number:d}: A parameter captured from the Gherkin step (here, an integer d). Behave uses regex or the parse library to extract parameters.
  • Calculator Class: We’ll need a simple implementation for this to work. Create calculator.py in the project root:
    class Calculator:  
        def __init__(self):  
            self.numbers = []  
    
        def enter_number(self, num):  
            self.numbers.append(num)  
    
        def add(self):  
            return sum(self.numbers)  

6. Running Tests and Interpreting Results

Run Behave

From the project root, run:

behave  

Sample Output

Feature: Add Two Numbers  # features/calculator.feature  
  As a user  
  I want to add two numbers  
  So that I can get their sum  

  Scenario: Add two positive numbers  # features/calculator.feature:4  
    Given I have entered 5 into the calculator  # features/steps/calculator_steps.py:5  
    And I have entered 3 into the calculator    # features/steps/calculator_steps.py:5  
    When I press add                            # features/steps/calculator_steps.py:12  
    Then the result should be 8 on the screen   # features/steps/calculator_steps.py:16  

1 feature passed, 0 failed, 0 skipped  
1 scenario passed, 0 failed, 0 skipped  
4 steps passed, 0 failed, 0 skipped, 0 undefined  

Failed Test Example

If we modify the Then step to expect 9 instead of 8, Behave will show a failure:

Then the result should be 9 on the screen   # features/steps/calculator_steps.py:16  
Assertion Error: Expected 9, got 8  

7. Advanced Behave Features

Background

Reduce repetition across scenarios with Background. It runs before every scenario in a feature file.

Example:

Feature: Add Two Numbers  
  Background:  
    Given a calculator is initialized  

  Scenario: Add two positive numbers  
    Given I have entered 5 into the calculator  
    And I have entered 3 into the calculator  
    When I press add  
    Then the result should be 8 on the screen  

  Scenario: Add positive and negative numbers  
    Given I have entered 5 into the calculator  
    And I have entered -3 into the calculator  
    When I press add  
    Then the result should be 2 on the screen  

Add a step definition for Given a calculator is initialized:

@given("a calculator is initialized")  
def step_impl(context):  
    context.calculator = Calculator()  

Scenario Outline (Data-Driven Testing)

Test multiple input combinations with Scenario Outline and Examples.

Example:

Scenario Outline: Add two numbers  
  Given I have entered <num1> into the calculator  
  And I have entered <num2> into the calculator  
  When I press add  
  Then the result should be <expected> on the screen  

  Examples:  
    | num1 | num2 | expected |  
    | 5    | 3    | 8        |  
    | -2   | 4    | 2        |  
    | 0    | 0    | 0        |  

Behave runs the scenario once for each row in Examples.

Tags

Tag scenarios or features to run specific tests (e.g., @smoke, @regression).

Example:

@smoke  
Scenario: Add two positive numbers  
  ...  

@regression  
Scenario Outline: Add two numbers  
  ...  

Run tagged scenarios:

behave --tags=@smoke  # Run only @smoke scenarios  
behave --tags=@smoke,@regression  # Run @smoke OR @regression  

Hooks

Hooks are Python functions that run before/after scenarios, features, or steps (e.g., setup/teardown). Define them in features/environment.py:

def before_scenario(context, scenario):  
    context.calculator = Calculator()  # Reinitialize calculator before each scenario  

def after_scenario(context, scenario):  
    del context.calculator  # Clean up after each scenario  

Common hooks: before_all, after_all, before_feature, after_feature, before_step, after_step.

8. Best Practices for BDD with Behave

  1. Keep Scenarios Focused: Each scenario should test one behavior. Avoid “kitchen sink” scenarios.
  2. Use Declarative Steps: Describe what, not how. Prefer “Then the user is logged in” over “Then the ‘Welcome’ div is visible”.
  3. Collaborate on Scenarios: Write feature files with product owners and testers to ensure alignment.
  4. Reuse Steps: Avoid duplicating steps. Use And/But or refactor into shared steps.
  5. Keep Data in Examples: Use Scenario Outline for data-driven testing instead of hardcoding values.
  6. Avoid UI Details: Focus on behavior, not UI elements (e.g., “clicks the button” vs. “presses Enter”).
  7. Review Regularly: Treat feature files as living documentation—update them as requirements change.

9. Conclusion

Behavior-Driven Development with Behave transforms how teams collaborate and validate software behavior. By writing human-readable scenarios in Gherkin and automating them with Python, you bridge the gap between technical and non-technical stakeholders, create maintainable tests, and ensure your software meets user needs.

Start small: pick a critical feature, collaborate on scenarios, and iterate. Over time, BDD will become a cornerstone of your development process, leading to more reliable software and happier teams.

10. References


This blog provides a comprehensive guide to BDD with Behave. Experiment with the examples, explore advanced features, and integrate BDD into your workflow to build better software! 🚀