Asercje w pytest - stabilne testy i szybsza diagnoza błędów

Wskaźniki LED w kolorach zielonym, żółtym i czerwonym, przypominające wyniki testów. Każdy wiersz to seria punktów, jak w `pytest assert`.

Autor

Dawid Kowalczyk

Opublikowano

13 lis 2026

Spis treści

Nieudany test powinien od razu pokazać, co nie działa, a nie zmuszać do przekopywania się przez logi. W pytest służy do tego prosta asercja oparta na słowie kluczowym assert, która pozwala sprawdzać wartości, typy, wyjątki i wyniki obliczeń. Pokażę, jak pisać takie warunki, kiedy używać pytest.raises() i pytest.approx(), a także jak unikać testów, które przechodzą przypadkiem.

Dobra asercja szybko pokazuje, co poszło nie tak

  • assert porównuje wynik działania z oczekiwaniem i zatrzymuje test przy niezgodności.
  • Pytest pokazuje wartości użyte w porównaniu, dlatego komunikaty błędów są zwykle bardziej użyteczne niż w standardowym unittest.
  • Do wyjątków służy pytest.raises(), a do liczb zmiennoprzecinkowych pytest.approx().
  • Najlepsze asercje sprawdzają jedno zachowanie i mają komunikat zrozumiały bez czytania implementacji.
  • W testach parametryzowanych ten sam warunek można bezpiecznie uruchomić dla wielu zestawów danych.

Jak działa pytest assert w teście Pythonowym

Najprostsza asercja ma postać assert warunek. Jeżeli warunek zwróci True, test trwa dalej. Gdy otrzymamy False, pytest zgłasza błąd AssertionError i przerywa wykonanie danej funkcji testowej.

def test_suma():
    wynik = 2 + 3
    assert wynik == 5

W praktyce nie sprawdzam wyłącznie samego wyniku. Zapisuję także dane pośrednie, gdy poprawia to czytelność testu. Taki styl ułatwia znalezienie przyczyny błędu, szczególnie gdy funkcja wykonuje kilka operacji.

def test_cena_po_rabacie():
    cena = 100
    rabat = 0.2
    wynik = cena * (1 - rabat)

    assert wynik == 80

Siłą pytest jest introspekcja asercji. Przy porównaniu assert wynik == 80 narzędzie pokaże między innymi rzeczywistą i oczekiwaną wartość. Nie trzeba więc za każdym razem pisać rozbudowanych metod w rodzaju assertEqual().

Najczęściej używane porównania

Cel Przykład Kiedy użyć
Równość assert status == 200 Gdy wynik powinien być dokładnie określony
Nierówność assert token != "" Gdy sprawdzasz, że wartość nie jest pusta lub niepoprawna
Przynależność assert "admin" in role_list Gdy element ma znajdować się w kolekcji
Typ assert isinstance(user_id, int) Gdy kontrakt funkcji obejmuje konkretny typ danych
Wartość logiczna assert response.ok Gdy obiekt udostępnia czytelny warunek logiczny

Unikam jednak sprawdzania zbyt wielu rzeczy w jednym wierszu. Asercja assert user.name and user.email and user.active może szybko wskazać, że coś jest nie tak, ale nie powie dokładnie co. Trzy krótsze warunki bywają mniej efektowne, za to znacznie szybsze w diagnozie.

Jak pisać czytelne asercje dla wartości i kolekcji

Najlepiej, gdy test opisuje zachowanie systemu, a nie szczegóły jego implementacji. Zamiast sprawdzać każdy wewnętrzny krok funkcji, porównuję jej publiczny rezultat z jasno określonym oczekiwaniem.

def test_normalizuje_nazwe():
    assert normalizuj_nazwe("  Ala  ") == "Ala"

Przy słownikach i listach pytest potrafi pokazać różnice między strukturami. To szczególnie wygodne przy testowaniu odpowiedzi API, konfiguracji albo danych zapisanych w pliku JSON.

def test_dane_uzytkownika():
    dane = pobierz_uzytkownika()

    assert dane == {
        "id": 7,
        "name": "Ala",
        "active": True,
    }

Jeżeli test nie powinien wymagać identycznej kolejności elementów, nie porównuję list bezpośrednio. W takim przypadku lepszy może być zbiór, sortowanie albo osobne sprawdzenie liczby elementów. Forma porównania powinna odpowiadać regułom biznesowym, a nie przypadkowemu formatowi danych.

def test_uprawnienia():
    uprawnienia = pobierz_uprawnienia()

    assert set(uprawnienia) == {"read", "write"}

Komunikaty pomagają, ale nie zastępują dobrego warunku

Do asercji można dodać własny komunikat. Jest przydatny wtedy, gdy sama różnica wartości nie wyjaśnia intencji testu.

assert wiek >= 18, "Użytkownik musi być pełnoletni"

Nie dodaję komunikatów mechanicznie do każdej asercji. Pytest i tak dostarcza szczegółowego raportu, a sztuczne teksty w rodzaju „warunek nie został spełniony” niczego nie wnoszą. Komunikat powinien wskazywać regułę lub kontekst biznesowy, którego nie widać w samym kodzie.

Wyjątki, liczby zmiennoprzecinkowe i wartości specjalne

Nie każdy test polega na sprawdzeniu wyniku funkcji. Czasem poprawne zachowanie oznacza odrzucenie danych i zgłoszenie konkretnego wyjątku. Wtedy nie używam zwykłego assert, bo musiałbym ręcznie przechwytywać błąd.

import pytest

def test_odrzuca_pusta_nazwe():
    with pytest.raises(ValueError):
        utworz_produkt("")

Można także sprawdzić treść komunikatu. To ma sens, gdy komunikat jest częścią kontraktu, na przykład jest wyświetlany użytkownikowi albo wykorzystywany przez inne narzędzie.

def test_wskazuje_bledny_format():
    with pytest.raises(ValueError, match="Nieprawidłowy format"):
        parsuj_date("2026/99/99")

Nie przywiązuję się jednak do całego komunikatu, jeśli może się on zmienić bez zmiany zachowania aplikacji. W takim przypadku wyrażenie regularne w parametrze match powinno sprawdzać tylko istotny fragment.

Dlaczego pytest.approx() jest lepsze od dokładnego porównania

Liczby zmiennoprzecinkowe często nie są przechowywane w pamięci dokładnie. Dlatego wynik działania 0.1 + 0.2 może różnić się od matematycznego 0.3 o bardzo małą wartość.

import pytest

def test_oblicza_procent():
    wynik = 0.1 + 0.2

    assert wynik == pytest.approx(0.3)

Domyślna tolerancja sprawdza, czy różnica mieści się w rozsądnym zakresie. Gdy test dotyczy finansów, pomiarów albo obliczeń naukowych, ustawiam tolerancję jawnie, ponieważ akceptowalny błąd wynika z domeny, a nie z samego pytest.

def test_pomiar_temperatury():
    temperatura = odczytaj_temperature()

    assert temperatura == pytest.approx(21.5, abs=0.1)

To rozwiązanie działa także dla sekwencji, słowników i tablic liczbowych. Nie stosowałbym go jednak do zwykłych tekstów, identyfikatorów ani kwot, które powinny być porównywane dokładnie, najlepiej jako typ Decimal.

Parametryzacja pozwala sprawdzać wiele przypadków jednym testem

Jeśli ta sama funkcja ma działać dla kilku danych wejściowych, nie kopiuję całej funkcji testowej. Dekorator pytest.mark.parametrize uruchamia ją wielokrotnie, a każda iteracja otrzymuje własne wartości.

import pytest

@pytest.mark.parametrize(
    "tekst, oczekiwany",
    [
        ("ala", "Ala"),
        ("  kot", "Kot"),
        ("DOM ", "Dom"),
    ],
)
def test_normalizuje_tekst(tekst, oczekiwany):
    assert normalizuj(tekst) == oczekiwany

Parametryzacja jest szczególnie cenna przy walidacji danych brzegowych. W jednym miejscu widać, jakie przypadki uznajemy za poprawne, a raport pytest potrafi wskazać konkretny zestaw argumentów, dla którego test się nie powiódł.

@pytest.mark.parametrize(
    "wiek, poprawne",
    [
        (0, False),
        (17, False),
        (18, True),
        (120, True),
        (121, False),
    ],
)
def test_waliduje_wiek(wiek, poprawne):
    assert czy_poprawny_wiek(wiek) is poprawne

Moim zdaniem lepiej dodać pięć sensownych przypadków niż kilkadziesiąt losowych. Największą wartość mają dane graniczne, puste, nietypowe i takie, które wcześniej powodowały błąd. Parametryzacja nie zastępuje przemyślanego scenariusza, tylko ułatwia jego zapis.

Typowe błędy przy stosowaniu asercji

Jednym z częstszych problemów jest asercja, która zawsze przechodzi, bo sprawdza nie to, co trzeba. Przykładem jest porównanie funkcji z samą sobą zamiast wywołanie jej i porównanie wyniku.

# Błąd
assert oblicz_rabat == oblicz_rabat

# Poprawnie
assert oblicz_rabat(100, 20) == 80

Inny problem to testowanie wartości typu truthy zamiast konkretnego rezultatu. Warunek assert lista sprawdza tylko, czy lista nie jest pusta. Jeśli interesuje mnie jej zawartość, powinienem porównać elementy albo długość.

# Tylko sprawdzenie, że coś istnieje
assert produkty

# Sprawdzenie konkretnego wyniku
assert produkty == ["chleb", "mleko"]

Uważam też za ryzykowne używanie assert w kodzie produkcyjnym do walidacji danych. Python może uruchomić program z opcją optymalizacji, przy której asercje zostaną pominięte. W aplikacji używaj do tego wyjątków, warunków i jawnej obsługi błędów, a assert zostaw głównie testom oraz niezmiennikom programistycznym.

Przeczytaj również: Chrome XPath - Pisz stabilne locatory. Testuj skutecznie.

Jak rozdzielać warunki w jednym teście

Test powinien mieć jeden główny powód niepowodzenia. Jeżeli sprawdzam jednocześnie status odpowiedzi, nagłówki i piętnaście pól JSON, później trudno ustalić, która reguła faktycznie się zepsuła.

def test_odpowiedz_api():
    odpowiedz = pobierz_dane()

    assert odpowiedz.status_code == 200
    assert odpowiedz.json()["active"] is True
    assert "id" in odpowiedz.json()

To nie oznacza, że każda funkcja testowa musi zawierać tylko jedną linijkę. Chodzi raczej o spójny scenariusz i krótkie, niezależne sprawdzenia. Gdy test rośnie do kilkudziesięciu asercji, zwykle jest to sygnał, że warto rozbić go na mniejsze przypadki.

Jak wykorzystać asercje w automatyzacji testów

Sama asercja nie automatyzuje procesu, jeśli testy nie są regularnie uruchamiane. W projekcie warto połączyć pytest z poleceniem wykonywanym lokalnie oraz w CI, czyli środowisku, które uruchamia testy po zmianie kodu.

pytest -q

Parametr -q ogranicza ilość komunikatów, ale nie ukrywa błędów. Przy diagnozie konkretnego przypadku przydają się także uruchomienie jednego pliku, jednej funkcji lub zatrzymanie po pierwszym niepowodzeniu.

pytest tests/test_users.py::test_odrzuca_pusta_nazwe -q
pytest -x

W pipeline nie traktuję zielonego wyniku jako dowodu, że aplikacja jest bezbłędna. Oznacza on tylko, że sprawdzone scenariusze przeszły. Jakość zależy od tego, czy asercje obejmują krytyczne ścieżki, przypadki brzegowe i błędne dane.

Najlepszy zestaw testów łączy proste porównania, sprawdzanie wyjątków, tolerancję dla obliczeń zmiennoprzecinkowych i parametryzację. Do tego dochodzi stabilne środowisko, powtarzalne dane oraz raport, który pozwala szybko znaleźć przyczynę problemu.

Mała checklista przed dodaniem kolejnej asercji

  • Czy sprawdzam rezultat widoczny dla użytkownika lub innego modułu, a nie przypadkowy szczegół implementacji?
  • Czy warunek jasno mówi, jakie zachowanie uznaję za poprawne?
  • Czy przy liczbach zmiennoprzecinkowych potrzebuję tolerancji?
  • Czy wyjątek jest częścią oczekiwanego zachowania i powinien być przechwycony przez pytest.raises()?
  • Czy dane obejmują przypadek pusty, graniczny i błędny?
  • Czy po niepowodzeniu będę wiedzieć, co konkretnie wymaga poprawy?

Dobrze napisana asercja jest mała, ale nie przypadkowa. Gdy opisuje jedno ważne zachowanie, korzysta z właściwego rodzaju porównania i daje czytelny raport, pytest staje się praktycznym narzędziem codziennej automatyzacji, a nie tylko kolejnym poleceniem uruchamianym przed wdrożeniem.

FAQ - Najczęstsze pytania

Zwykłe assert służy do porównywania wyników, typów, wartości i warunków logicznych. pytest.raises() stosuj wtedy, gdy poprawne zachowanie polega na zgłoszeniu konkretnego wyjątku, na przykład ValueError. Parametr match pozwala dodatkowo sprawdzić istotny fragment komunikatu błędu.

Liczby zmiennoprzecinkowe mogą nie być przechowywane dokładnie, dlatego wynik 0.1 + 0.2 nie zawsze jest równy matematycznemu 0.3. pytest.approx() uwzględnia tolerancję, a przy pomiarach, finansach lub obliczeniach naukowych można ustawić ją jawnie, na przykład abs=0.1. Nie należy używać tej metody do tekstów, identyfikatorów ani dokładnych kwot.

Ryzykownym przykładem jest porównanie funkcji z samą sobą, takie jak assert oblicz_rabat == oblicz_rabat, zamiast wywołania funkcji z argumentami i sprawdzenia wyniku. Podobnie assert lista kontroluje tylko, czy lista nie jest pusta, a nie jej zawartość. Warto też pamiętać, że assert w kodzie produkcyjnym może zostać pominięty przy uruchomieniu Pythona z optymalizacją.

Dekorator pytest.mark.parametrize pozwala uruchomić tę samą funkcję testową dla wielu zestawów danych. Warto uwzględniać przypadki puste, nietypowe, błędne i graniczne, na przykład wiek 0, 17, 18, 120 oraz 121. Raport pytest wskaże konkretny zestaw argumentów, dla którego test się nie powiódł.

Oceń artykuł

Ocena: 5.00 Liczba głosów: 1

Tagi:

pytest asercje parametryzacja wyjątki liczby zmiennoprzecinkowe

Udostępnij artykuł

Dawid Kowalczyk

Dawid Kowalczyk

Nazywam się Dawid Kowalczyk i od 6 lat zajmuję się automatyzacją testów oraz zapewnieniem jakości oprogramowania. Moje zainteresowanie tymi tematami zrodziło się z potrzeby zrozumienia, jak kluczowe jest dostarczanie produktów wysokiej jakości w dzisiejszym świecie technologii. Lubię dzielić się swoją wiedzą i doświadczeniem, pomagając innym w zrozumieniu złożonych zagadnień związanych z QA i automatyzacją. W moich tekstach staram się przedstawiać praktyczne rozwiązania, porównując różne metody i narzędzia, które mogą ułatwić pracę w branży. Zawsze stawiam na rzetelne źródła informacji oraz aktualne trendy, aby moje artykuły były nie tylko zrozumiałe, ale i przydatne. Wierzę, że jasna i uporządkowana prezentacja wiedzy jest kluczem do skutecznej nauki i rozwoju w obszarze jakości oprogramowania.

Napisz komentarz