NashTech Blog

Table of Contents

What is Behave?

Behave is a Python implementation of Cucumber, specifically designed for Behavior-Driven Development (BDD). It allows you to write tests in natural language using Gherkin syntax, making tests readable by both technical and non-technical stakeholders.

Core Components of Behave

1. Feature Files

Feature files are written in Gherkin syntax and describe the behavior of your application. They are stored with a *.feature extension.

# login.feature
Feature: User Authentication
    In order to access protected areas
    As a registered user
    I want to be able to log in

    @smoke
    Scenario: Successful login with valid credentials
        Given I am on the login page
        When I enter valid credentials
        And I click the login button
        Then I should be logged in successfully

    Scenario Outline: Login with different credentials
        Given I am on the login page
        When I enter "<username>" and "<password>"
        Then I should see "<message>"

        Examples:
            | username | password | message           |
            | valid   | valid    | Welcome!          |
            | invalid | valid    | Unknown user      |
            | valid   | invalid  | Incorrect password|

2. Step Definitions with Page Objects

Step definitions connect Gherkin steps to Python code. Using Page Objects helps create more maintainable and reusable code.

# pages/base_page.py
class BasePage:
    def __init__(self, driver):
        self.driver = driver
    
    def find_element(self, locator):
        return self.driver.find_element(*locator)
    
    def type_text(self, locator, text):
        self.find_element(locator).send_keys(text)
    
    def click(self, locator):
        self.find_element(locator).click()
    
    def get_text(self, locator):
        return self.find_element(locator).text
# pages/login_page.py
from selenium.webdriver.common.by import By
from .base_page import BasePage

class LoginPage(BasePage):
    # Locators as class attributes
    USERNAME_FIELD = (By.ID, 'username')
    PASSWORD_FIELD = (By.ID, 'password')
    LOGIN_BUTTON = (By.ID, 'login-button')
    WELCOME_MESSAGE = (By.ID, 'welcome-text')
    
    def navigate_to(self):
        self.driver.get('http://example.com/login')
    
    def enter_credentials(self, username, password):
        self.type_text(self.USERNAME_FIELD, username)
        self.type_text(self.PASSWORD_FIELD, password)
    
    def click_login(self):
        self.click(self.LOGIN_BUTTON)
    
    def get_welcome_message(self):
        return self.get_text(self.WELCOME_MESSAGE)
# steps/login_steps.py
from behave import given, when, then
from pages.login_page import LoginPage

@given('I am on the login page')
def step_impl(context):
    context.login_page = LoginPage(context.driver)
    context.login_page.navigate_to()

@when('I enter valid credentials')
def step_impl(context):
    context.login_page.enter_credentials('user@example.com', 'password123')
    context.login_page.click_login()

@then('I should be logged in successfully')
def step_impl(context):
    welcome_message = context.login_page.get_welcome_message()
    assert 'Welcome' in welcome_message

3. Environment Controls (environment.py)

The environment.py file is a powerful feature of Behave that provides hooks for test setup and teardown at different levels:

# environment.py

def before_all(context):
    # Runs before any features or scenarios
    context.config.setup_logging()
    # Setup your test environment
    context.base_url = 'http://example.com'

def before_feature(context, feature):
    # Runs before each feature
    if 'browser' in feature.tags:
        context.browser = create_browser()

def before_scenario(context, scenario):
    # Runs before each scenario
    context.data = {}  # Reset test data

def before_step(context, step):
    # Runs before each step
    context.step_start_time = time.time()

def after_step(context, step):
    # Runs after each step
    if step.status == "failed":
        take_screenshot(context)

def after_scenario(context, scenario):
    # Runs after each scenario
    cleanup_test_data(context)

def after_feature(context, feature):
    # Runs after each feature
    if hasattr(context, 'browser'):
        context.browser.quit()

def after_all(context):
    # Runs after all features are completed
    generate_test_report()

4. Behave Configuration

Behave can be configured through behave.ini or command-line arguments:

[behave]
# Define feature files location
paths = features

# Define steps directory
steps_directory = steps

# Show skipped steps in output
show_skipped = true

# Stop on first failure
stop = true

# Show timing information
show_timings = true

# Format output (can be pretty, plain, json, etc.)
format = pretty

# Don't capture stdout
capture = false

# Use JUnit format for CI integration
junit = true
junit_directory = reports/junit

5. Context and Page Objects – Sharing Data Between Steps

The context object is a key feature in Behave that allows sharing data and page objects between steps:

# models/user.py
from dataclasses import dataclass

@dataclass
class User:
    name: str
    email: str
    age: int
# pages/user_page.py
from .base_page import BasePage
from selenium.webdriver.common.by import By

class UserPage(BasePage):
    NAME_FIELD = (By.ID, 'name')
    EMAIL_FIELD = (By.ID, 'email')
    AGE_FIELD = (By.ID, 'age')
    SUBMIT_BUTTON = (By.ID, 'submit')
    
    def fill_user_form(self, user):
        self.type_text(self.NAME_FIELD, user.name)
        self.type_text(self.EMAIL_FIELD, user.email)
        self.type_text(self.AGE_FIELD, str(user.age))
    
    def submit_form(self):
        self.click(self.SUBMIT_BUTTON)
# steps/user_steps.py
from behave import given, when, then
from models.user import User
from pages.user_page import UserPage

@given('I have initial user data')
def step_impl(context):
    # Store model object in context
    context.user = User(
        name='John Doe',
        email='john@example.com',
        age=30
    )
    context.user_page = UserPage(context.driver)
    
    # Parse table data into model objects if provided
    if hasattr(context, 'table'):
        context.users = [
            User(
                name=row['name'],
                email=row['email'],
                age=int(row['age'])
            ) for row in context.table
        ]

@when('I process the user data')
def step_impl(context):
    context.user_page.fill_user_form(context.user)
    context.user_page.submit_form()

@then('I should see the user profile')
def step_impl(context):
    displayed_name = context.user_page.get_text(UserPage.NAME_FIELD)
    assert displayed_name == context.user.name

6. Using Tags

Tags are powerful features in Behave for organizing and selecting tests:

@web @login
Feature: User Login
    
    @smoke @fast
    Scenario: Quick login check
        Given I am on the login page
        When I enter valid credentials
        Then I should be logged in

    @slow @regression
    Scenario: Detailed login validation
        Given I am on the login page
        When I enter valid credentials
        Then I should see welcome message
        And my user preferences should be loaded

Run specific tags:

# Run scenarios tagged with @smoke
behave --tags=@smoke

# Run scenarios with multiple tags
behave --tags=@web --tags=@smoke

# Skip certain tags
behave --tags=~@slow

# Complex tag expressions
behave --tags="@smoke and not @slow"

7. Using Step Parameters

Behave supports different types of step parameters:

# Step with quoted string
@given('I search for "{term}"')
def step_impl(context, term):
    context.search_term = term
# Step with numbers
@then('I should see {count:d} results')
def step_impl(context, count):
    assert len(context.results) == count
# Step with custom type
from behave import register_type
import parse

@parse.with_pattern(r"\d+")
def parse_number(text):
    return int(text)

register_type(Number=parse_number)

@then('I should see {count:Number} items')
def step_impl(context, count):
    assert len(context.items) == count

8. Using Tables and Multi-line Text

Behave provides special support for tables and multi-line text:

Scenario: User registration with details
    Given I have the following user details:
        | name  | email           | age |
        | John  | john@email.com  | 30  |
        | Jane  | jane@email.com  | 25  |
    And I have the following address:
        """
        123 Test Street
        Test City, 12345
        Test Country
        """
    When I register the users
    Then they should be in the database
@given('I have the following user details')
def step_impl(context):
    # context.table is a table object
    context.users = []
    for row in context.table:
        context.users.append({
            'name': row['name'],
            'email': row['email'],
            'age': int(row['age'])
        })

@given('I have the following address')
def step_impl(context):
    # context.text contains the multi-line text
    context.address = context.text

Running Tests

Behave provides various command-line options for test execution:

# Run all features
behave

# Run specific feature
behave features/login.feature

# Run specific scenario by line number
behave features/login.feature:10

# Generate JSON output
behave --format=json --outfile=report.json

# Run in parallel using parallel-behave – Note: You must install parallel-behave via pip: pip install parallel-behave
parallel-behave -p 4 features/

# Debug with more verbose output
behave --format=pretty --verbose

Conclusion

Behave is a powerful BDD framework that brings together the best of both worlds – readable specifications and Python’s simplicity. By understanding and utilizing its features effectively, you can create maintainable, readable, and robust test suites that serve as living documentation for your application.

The key to success with Behave is to:
– Write clear, focused feature files
– Create reusable step definitions
– Use the context object effectively
– Leverage hooks for proper setup and teardown
– Organize your project structure well
– Use tags for test organization and execution control

With these practices in place, Behave can help you build a strong BDD framework that supports your development process and improves communication between technical and non-technical team members.

Picture of thinhnguyenphamphu

thinhnguyenphamphu

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top