Test Automation Lesson 4 of 6 ⏱️ 4 min read

Data-Driven & BDD Testing

Running one test against many inputs with pytest parametrize, and writing Gherkin scenarios with pytest-bdd.

Updated Oct 30, 2026

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:

Python
# 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:

Text
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:

Text
# 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!
Python
# 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).
Gherkin
# 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

Bash
pip install pytest-bdd
Python
# 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.

Finished this lesson?
Tick it off to track your Test Automation progress.
Next lesson