Testy BDD w Pytest czyli jak zbliżyć kod do biznesu

Automatyczne

Loading

Artykuł Testy BDD w Pytest czyli jak zbliżyć kod do biznesu to kolejny wpis pozwalający Wam wpływać na jakość tworzonego oprogramowania w procesie developmentu. Testy BDD w Pytest pozwalają tworzyć zrozumiałe i czytelne przypadki testowe przy użyciu języka Gherkin. W tym artykule odpowiemy na pytanie:
W jaki sposób BDD pomaga znaleźć wspólny język testerom, biznesowi i programistom?

Wprowadzenie

BDD, czyli Behavior-Driven Development, to metoda projektowania i testowania funkcjonalności oparta na opisie zachowania systemu z punktu widzenia użytkownika końcowego.

Scenariusze pisane w stylu BDD nie są tylko testami — to czytelna dokumentacja oczekiwań biznesowych, którą rozumie zarówno tester, developer, jak i osoba nietechniczna.

Dla testera oznacza to, że:

  • Można pisać testy, które są zrozumiałe także dla osób spoza IT
  • Można współtworzyć scenariusze z analitykiem lub właścicielem produktu
  • Łatwiej jest wykryć nieścisłości w wymaganiach zanim powstanie kod
  • Testy BDD mogą służyć jako żywa dokumentacja — zawsze aktualna i testowana automatycznie

W dalszej części artykułu zostaną przedstawione zalety podejścia BDD.

💡Z jakimi problemami pomagają uporać się Testy BDD w Pytest?

W codziennej pracy zespołów projektowych często pojawiają się nieporozumienia między tym, co zostało zdefiniowane w wymaganiach, a tym, co zostało ostatecznie zaimplementowane. Testerzy mają trudności ze zrozumieniem intencji biznesu, programiści interpretują wymagania po swojemu, a osoby nietechniczne nie potrafią odczytać języka testów automatycznych.

BDD odpowiada na te wyzwania, wprowadzając wspólny, czytelny język opisu funkcjonalności, który łączy wszystkich interesariuszy: testerów, developerów, analityków i biznes.

Dzięki opisowi scenariuszy w formacie Given–When–Then:

  • łatwiej zrozumieć, co ma się dziać w aplikacji z perspektywy użytkownika
  • szybciej można wykryć luki lub sprzeczności w wymaganiach
  • testy stają się czytelną dokumentacją zachowania systemu

🔍 Czym są Testy BDD w Pytest?

Testy BDD (Behavior-Driven Development) opierają się na opisie zachowania systemu z perspektywy użytkownika. Każdy scenariusz testowy pisany jest w formacie Given–When–Then (Zakładając, że – Kiedy – Wtedy), co pozwala opisać oczekiwane działanie funkcji w sposób zrozumiały dla wszystkich członków zespołu.

W Pythonie do implementacji testów BDD często wykorzystuje się bibliotekę pytest w połączeniu z rozszerzeniem pytest-bdd. Pozwala ono na:

  • definiowanie scenariuszy w plikach .feature (w stylu Gherkin),
  • mapowanie kroków Given, When, Then na funkcje testowe w Pythonie,
  • integrację testów BDD z istniejącym zestawem testów pytest.

Testy BDD w pytest działają jak zwykłe testy automatyczne, ale ich struktura i sposób opisu sprawiają, że łatwiej je czytać, utrzymywać i omawiać – nawet w gronie osób nietechnicznych.

🐍 Jak pisać Testy BDD w Pytest?

Pisanie testów BDD w pytest wymaga innego podejścia niż tradycyjne testy jednostkowe czy integracyjne. Zamiast skupiać się na strukturze kodu, koncentrujemy się na zachowaniu systemu z punktu widzenia użytkownika.

🔧 Od czego zacząć?

🐍 Zainstaluj bibliotekę pytest-bdd
pip install pytest-bdd
🥒 Utwórz plik z rozszerzeniem .feature

Plik .feature zawiera scenariusze testowe zapisane w języku Gherkin. Wykorzystuje słowa kluczowe takie jak: Feature, Scenario, Given, When, Then, And, But do opisu warunków testowych i oczekiwanych rezultatów.
Poniżej zostanie przedstawiona lista słów kluczowych oraz ich znacznie.

  • Feature – opisuje funkcjonalność lub moduł, którego dotyczą scenariusze
  • Scenario – pojedynczy przypadek testowy opisujący konkretne zachowanie systemu
  • Given – definiuje stan początkowy przed wykonaniem akcji (np. użytkownik jest zalogowany)
  • When – opisuje akcję użytkownika lub systemu (np. kliknięcie przycisku, wysłanie formularza)
  • Then – definiuje oczekiwany rezultat lub stan systemu po wykonaniu akcji
  • And – słowo pomocnicze, pozwala dodać kolejne kroki do Given, When, lub Then
  • But – alternatywa dla And używana do wskazania wyjątku lub zachowania przeciwnego
  • Background – sekcja zawierająca wspólne kroki dla wszystkich scenariuszy w pliku
  • Scenario Outline – szablon scenariusza z parametrami, pozwala testować wiele wariantów danych wejściowych
  • Examples – tabela danych używanych w Scenario Outline do testowania różnych przypadków

Przykładowy plik zawierający scenariusz logowania z nieprawidłowymi danymi

Feature: User Authentication

  Scenario: Login with invalid credentials
    Given I am on the login page
    When I enter an invalid email "invalid@example.com"
    And I enter an invalid password "wrongpassword"
    And I click the login button
    Then I should see an error message "Authentication failed."
✍️ Przygotuj Page Object reprezentujący stronę logowania w aplikacji testowanej

Plik ten reprezentuje warstwę abstrakcji nad interfejsem użytkownika, oddzielając logikę testów od szczegółów technicznych strony. Umożliwia łatwe i czytelne odwoływanie się do elementów strony logowania w scenariuszach testowych BDD.

Przykładowy plik

from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException, TimeoutException


class LoginPage:
    def __init__(self, browser: WebDriver) -> None:
        self.browser = browser
        self.email_input = (By.ID, "email")
        self.password_input = (By.ID, "passwd")
        self.login_button = (By.ID, "SubmitLogin")
        self.error_message = (By.XPATH, "//div[contains(@class, 'alert-danger')]")
        self.account_section = (By.CLASS_NAME, "account")

    def enter_email(self, email: str) -> None:
        try:
            self.browser.find_element(*self.email_input).send_keys(email)
        except NoSuchElementException:
            raise Exception("Email input field was not found on the page.")

    def enter_password(self, password: str) -> None:
        try:
            self.browser.find_element(*self.password_input).send_keys(password)
        except NoSuchElementException:
            raise Exception("Password input field was not found on the page.")

    def click_login(self) -> None:
        try:
            self.browser.find_element(*self.login_button).click()
        except NoSuchElementException:
            raise Exception("Login button was not found on the page.")

    def is_logged_in(self) -> bool:
        try:
            WebDriverWait(self.browser, 5).until(
                EC.visibility_of_element_located(self.account_section)
            )
            return True
        except TimeoutException:
            return False

    def get_error_message(self) -> str:
        try:
            return self.browser.find_element(*self.error_message).text.strip()
        except NoSuchElementException:
            return ""
🧪 Zaimplementuj kroki testowe

Plik z krokami testowymi (steps) łączy scenariusze zapisane w Gherkinie z konkretnymi działaniami w kodzie. Każdy krok (Given, When, Then) jest odwzorowany jako funkcja, która wykonuje operacje na stronie lub sprawdza oczekiwany rezultat. Dzięki temu scenariusze .feature stają się automatycznymi testami wykonującymi rzeczywiste akcje w aplikacji.

Przykładowy plik

import pytest
from pages.login_page import LoginPage
from pytest_bdd import given, parsers, scenarios, then, when
from selenium.webdriver.remote.webdriver import WebDriver
from utils.browser import wait_for_page_load

scenarios("../features/login.feature")


@pytest.fixture
def login_page(browser: WebDriver) -> LoginPage:
    return LoginPage(browser)


@given("I am on the login page")
def open_login_page(browser: WebDriver) -> None:
    browser.get(
        "http://www.automationpractice.pl/index.php?controller=authentication&back=my-account"
    )
    wait_for_page_load(browser)


@when(parsers.parse('I enter an invalid email "{email}"'))
def enter_invalid_email(login_page: LoginPage, email: str) -> None:
    login_page.enter_email(email)


@when(parsers.parse('I enter an invalid password "{password}"'))
def enter_invalid_password(login_page: LoginPage, password: str) -> None:
    login_page.enter_password(password)


@when("I click the login button")
def click_login_button(login_page: LoginPage) -> None:
    login_page.click_login()


@then(parsers.parse('I should see an error message "{message}"'))
def verify_error_message(login_page: LoginPage, message: str) -> None:
    error_message = login_page.get_error_message()
    assert (
        message in error_message
    ), f"❌ Expected error message '{message}', but got '{error_message}'"

📁 Struktura projektu powinna wyglądać zgodnie z poniżej przedstawioną

project_root/
│
├── features/              
│   └── login.feature       
│
├── pages/                  
│   └── login_page.py       
│
├── steps/                  
    └── login_steps.py      

📌 Opis katalogów

features/ – tu znajdują się pliki .feature pisane w Gherkinie

steps/ – implementacje kroków scenariuszy (@given, @when, @then)

pages/ – wzorzec Page Object: klasy i metody do operacji na interfejsie użytkownika.

Jak uruchomić test?

Jeśli chcesz uruchomić wszystkie testy w projekcie przeklej poniższe polecenie do termianala

pytest

Jeśli chcesz uruchomić wszystkie scenariusze w katalogu steps skorzystaj z poniższego:

pytest tests/steps/

💣 Dobre praktyki BDD

🛠️ Unikaj zbyt technicznych opisów scenariuszy. Zamiast „klikam przycisk submit”, użyj „wysyłam formularz rejestracji”. Scenariusze mają być zrozumiałe dla każdego członka zespołu.

🛠️ Grupuj scenariusze tematycznie. Jeden plik .feature powinien odpowiadać jednej funkcjonalności lub modułowi, co ułatwia ich przeszukiwanie i zarządzanie.

🛠️ Używaj Background, gdy kroki się powtarzają. Dzięki temu unikniesz duplikacji kodu w scenariuszach i poprawisz ich czytelność.

🛠️ Zadbaj o spójność nazw kroków. Nazwy kroków powinny być jednoznaczne, zwięzłe i powtarzalne. Jeden krok – jedna funkcja.

🛠️ Stosuj parametryzację przy wielu zestawach danych. Jeśli testujesz tę samą funkcję dla różnych danych wejściowych, skorzystaj z Scenario Outline i tabeli Examples. Umożliwia to wielokrotne wykonanie tego samego scenariusza bez duplikowania kodu.


📌 Podsumowanie

Na zakończenie artykułu Artykuł Testy BDD w Pytest czyli jak zbliżyć kod do biznesu to trzeba wskazać, że Testy BDD w Pytest niosą ze sobą wiele praktycznych zalet – zarówno dla zespołów testerskich, jak i deweloperskich czy biznesowych:

  • Czytelność testów
    Scenariusze pisane w stylu Given–When–Then są zrozumiałe nie tylko dla programistów, ale również dla analityków czy właścicieli produktu.
  • Lepsza komunikacja
    Testy BDD stają się punktem odniesienia w rozmowach o wymaganiach. Pomagają zespołowi pracować na wspólnym „języku funkcjonalności”.
  • Wcześniejsze wykrywanie błędów w wymaganiach
    Tworzenie scenariuszy BDD często ujawnia nieścisłości lub luki w oczekiwaniach jeszcze przed rozpoczęciem implementacji.
  • Automatyczna dokumentacja
    Scenariusze .feature mogą służyć jako żywa dokumentacja – zawsze aktualna i łatwa do śledzenia przez cały cykl życia projektu.
  • Łatwiejsze utrzymanie testów
    Dobrze opisane scenariusze pozwalają szybciej zrozumieć, co test ma robić – nawet po wielu miesiącach.

Autor
Honorata Łyczak 🐞
Linkedin