BDD's fundamental importance lies in the fact that it allows non-technical stakeholders, such as business analysts, to write instructions that are easy for developers to understand.
Having a common language that the developer team, business analysts, the product owner, and project manager can all understand makes collaboration easier.
Thus, misunderstandings are avoided throughout the development process, and every detail is correctly aligned.
With BDD, misunderstandings and misinterpretations pose less of a risk in requirements. Hence, development processes become more efficient, and software products are of higher quality.
In this article, we will delve deep into pytest-bdd, a Python framework, and explore how to seamlessly combine Gherkin syntax with pytest to provide a robust solution for BDD in Python projects. This article assumes familiarity with the basics of the pytest library and TDD framework.
pytest-bdd and Gherkin Logic
pytest-bdd is a pytest plugin explicitly designed to run BDD-style testing using Gherkin syntax. It combines the simplicity and flexibility of pytest with the readability and exposure of Gherkin. Before we explore how to use pytest-bdd properly, let’s first understand the basics of Gherkin syntax.
Gherkin is a simple, human-readable language that allows you to describe the behavior of your software in a structured format.
This structured format is essential for clear communication among stakeholders, including developers, business analysts, and testers. It follows a set of keywords and syntax rules to define features, scenarios, and steps. Understanding these keywords is essential for effectively writing and interpreting behavior-driven development scenarios. Here are the essential Gherkin keywords:
Feature: Descriptive text of the feature
Defines a feature or functionality of the software being tested.
Scenario: Descriptive text of the scenario
Describes a specific test scenario or use case within the feature.
Given: <some initial condition>
Specifies the initial state or precondition of the system before the action is performed.
When: <some action is performed>
Indicates the action or event that triggers a change in the system.
Then: <some expected outcome is observed>
Defines the expected outcome or result of the action performed in the scenario.
And/But:
Additional keywords used to continue the flow of steps within a scenario.
Gherkin syntax allows for writing clear and concise descriptions of features and scenarios using plain language. Each scenario consists of a series of steps structured around the keywords Given, When, and Then, which help to define the context, action, and outcome of the scenario respectively.
By using pytest-bdd, you can write your test scenarios in Gherkin format and execute them as part of your pytest test suite. This integration enables you to leverage the power of BDD-style testing while harnessing the flexibility and extensibility of pytest for writing and organizing your tests.
Here’s a basic example scenario:
Feature: User Login
Scenario: User logs in with correct credentials
Given the user is on the login page
When the user enters valid username and password
Then the user should be logged in successfully
This scenario should be written in a .feature file and then passed to pytest-bdd.
Rules
Here are some important considerations when using pytest-bdd.
Gherkin Syntax: Utilize Gherkin syntax to define conditions and phases, including Given, When, Then, And, and But statements.
Step Definition: Use steps as Python functions. pytest-bdd matches steps in attribute files with their corresponding step definitions, which can be parameterized or defined as regular Python functions.
Fixtures: Utilize the pytest-bdd and pytest fixture programs together to set up and break down the environment for testing.
Testing: Treat each scenario as an individual test case. Run the BDD test using the standard pytest command.
Scenario Outlines: Define scenario templates with placeholder values using Scenario Outlines. Parameterize the steps in the Scenario Outlines using pytest.mark.parametrize.
(Not covered in the article)
Tags: Tag scenarios and attributes using pytest.mark to select a test based on a tag.
Hooks: Use pytest-bdd hooks to generate code before or after events in the BDD test lifecycle.
Reports: Seamlessly integrate with pytest’s reporting capabilities to create detailed test reports and summaries.
Implementing BDD with pytest-bdd: A Practical Example
Let’s consider a simplified example of how to use pytest-bdd to test a calculator application. We will define conditions for sum and multiplication operations using Python.
Here’s what the .feature file would look like:
Feature: Calculator Addition
Scenario: Adding two numbers
Given I have two numbers
When I add the numbers
Then I should get the sum
And the corresponding Python code:
from pytest_bdd import scenario, given, when, then
@scenario('calculator.feature', 'Addition')
def test_addition():
pass
@given("I have two numbers")
def step_given_numbers():
number1 = 5
number2 = 3
return number1, number2
@when("I add the numbers")
def step_when_add_numbers(step_given_numbers):
number1, number2 = step_given_numbers
result = number1 + number2
return result
@then("I should get the sum")
def step_then_sum(step_when_add_numbers):
assert step_when_add_numbers == 8
This example illustrates a procedural scenario where we define a given preparer, time, and then define a step. Each step corresponds to a specific action or loyalty in the situation.
Extending to Scenario Outlines
We can extend our BDD tests to handle more cases using the scenario framework. BDD offers scenario outlines to parameterize scenarios to save on lines of code written. An example is provided below.
Consider a shopping cart. We have several items we want to add to this cart. The variable “thing to be added to the cart” will be used several times and the nature of the variable will stay the same, only the thing we add to the cart will change.
Here’s a sample .feature file:
Feature: Shopping Cart
As a customer
I want to add products to my shopping cart
So that I can purchase them later
Scenario Outline: Add products to the shopping cart
Given I have an empty shopping cart
When I add <product> to the cart
Then the cart should contain <product>
Examples:
| product |
| Apples |
| Bananas |
| Oranges |
And here’s the Python code:
import pytest
@pytest.fixture
def shopping_cart():
return []
@given("I have an empty shopping cart")
def empty_shopping_cart(shopping_cart):
assert len(shopping_cart) == 0
@when("I add {product} to the cart")
def add_product(shopping_cart, product):
shopping_cart.append(product)
@then("the cart should contain {product}")
def verify_cart_contents(shopping_cart, product):
assert product in shopping_cart
Note that the parameter names have to be same in both .feature file and the Python script (in our case, “product”) and the parameter should be denoted with curly brackets in the strings we pass to the BDD decorators in the Python script.
Conclusion
pytest-bdd, along with Gherkin syntax, provides a powerful framework for implementing BDD in Python projects. By writing tests in a clear, human-readable format, teams can maximize collaboration and ensure software meets stakeholder expectations. Using pytest-bdd, you can unlock the full capabilities of BDD and develop robust, high-quality software programs.
Start utilizing the power of BDD in your Python projects with pytest-bdd and Gherkin today!