Data-Driven & BDD Testing
Running one test against many inputs with pytest parametrize, and writing Gherkin scenarios with pytest-bdd.
Data-driven testing
Data-driven testing separates test logic from test data: one test function runs once for every row of inputs and expected results. It's how you turn the test design techniques — equivalence partitions, boundary values, decision tables — into automated checks without copy-pasting tests.
pytest parametrize
Using the page objects from the previous lesson:
# test_login_data.py — run with: pytest -v
import pytest
from pages.login_page import LoginPage
LOGIN_CASES = [
# username, password, expected message
("tomsmith", "SuperSecretPassword!", "You logged into a secure area!"),
("tomsmith", "wrong-password", "Your password is invalid!"),
("someone", "SuperSecretPassword!", "Your username is invalid!"),
("", "", "Your username is invalid!"),
]
@pytest.mark.parametrize("username, password, expected", LOGIN_CASES,
ids=["valid", "wrong-password", "unknown-user", "empty"])
def test_login_messages(driver, username, password, expected):
login = LoginPage(driver).open()
login.login(username, password)
assert expected in login.flash_message()
pytest reports each row as its own test, so a failure tells you exactly which input broke:
test_login_data.py::test_login_messages[valid] PASSED
test_login_data.py::test_login_messages[wrong-password] PASSED
test_login_data.py::test_login_messages[unknown-user] PASSED
test_login_data.py::test_login_messages[empty] PASSED
Loading data from a CSV file
When testers or business analysts maintain the data, keep it outside the code:
# login_cases.csv
username,password,expected
tomsmith,SuperSecretPassword!,You logged into a secure area!
tomsmith,wrong-password,Your password is invalid!
someone,SuperSecretPassword!,Your username is invalid!
# test_login_csv.py
import csv
from pathlib import Path
import pytest
from pages.login_page import LoginPage
def load_cases():
path = Path(__file__).parent / "login_cases.csv"
with path.open(newline="", encoding="utf-8") as f:
return [(row["username"], row["password"], row["expected"]) for row in csv.DictReader(f)]
@pytest.mark.parametrize("username, password, expected", load_cases())
def test_login_from_csv(driver, username, password, expected):
login = LoginPage(driver).open()
login.login(username, password)
assert expected in login.flash_message()
Adding a test case is now a one-line CSV change.
Behaviour-Driven Development (BDD)
BDD describes behaviour in plain-language scenarios that business people, developers and testers all understand. Scenarios are written in Gherkin:
Feature— the capability being described.Scenario— one example of behaviour.Given(context),When(action),Then(outcome),And/But(more of the same).
# features/login.feature
Feature: Login
Registered users can sign in to reach the secure area.
Scenario: Successful login
Given I am on the login page
When I log in as "tomsmith" with password "SuperSecretPassword!"
Then I see the message "You logged into a secure area!"
Scenario Outline: Rejected login
Given I am on the login page
When I log in as "<username>" with password "<password>"
Then I see the message "<message>"
Examples:
| username | password | message |
| tomsmith | wrong-password | Your password is invalid! |
| someone | SuperSecretPassword! | Your username is invalid! |
Step definitions with pytest-bdd
pip install pytest-bdd
# test_login_bdd.py — run with: pytest -v
from pytest_bdd import given, parsers, scenarios, then, when
from pages.login_page import LoginPage
scenarios("features/login.feature")
@given("I am on the login page", target_fixture="login_page")
def open_login_page(driver):
return LoginPage(driver).open() # returned value becomes the `login_page` fixture
@when(parsers.parse('I log in as "{username}" with password "{password}"'))
def log_in(login_page, username, password):
login_page.login(username, password)
@then(parsers.parse('I see the message "{message}"'))
def see_message(login_page, message):
assert message in login_page.flash_message()
Each Gherkin step maps to one Python function, and the page objects do the real work. Other popular BDD tools: Cucumber (Java/JS), SpecFlow/Reqnroll (.NET) and Behave (Python).
When BDD helps — and when it doesn't
BDD shines when product owners actually read and help write the scenarios: the .feature files become living documentation and acceptance criteria. If only testers ever read them, Gherkin adds a translation layer without much benefit — plain parametrized tests are simpler.
Next, run all of this automatically on every change with CI integration.