Cześć! W poprzedniej lekcji poznaliśmy listy, słowniki, zbiory i krotki — struktury, w których będziesz przechowywać dane testowe, odpowiedzi API i oczekiwane rezultaty. Teraz zrobimy kolejny ważny krok: zamiast powielać operacje na tych danych w wielu miejscach, zamkniemy je w funkcjach.
Funkcja to nazwany fragment kodu, który można uruchamiać wielokrotnie, przekazując mu różne dane. W automatyzacji testów funkcje są podstawą utrzymywalności: mogą np. budować dane użytkownika, sprawdzać fragment odpowiedzi API lub przygotowywać powtarzalny stan testowy.
Po tej lekcji będziesz umieć definiować funkcje, przekazywać do nich argumenty pozycyjne, nazwane i domyślne oraz zwracać wyniki za pomocą return.
Funkcja: definicja nie jest jeszcze wykonaniem
Najprostsza funkcja w Pythonie ma:
- słowo kluczowe
def, - nazwę,
- nawiasy z parametrami,
- dwukropek,
- wcięte ciało.
def show_test_started():
print("Test rozpoczęty")
Ta instrukcja definiuje funkcję, ale jeszcze jej nie uruchamia. Aby kod w środku został wykonany, trzeba funkcję wywołać:
show_test_started()
Wynik:
Test rozpoczęty

W Pythonie wcięcie jest częścią składni. Wszystko, co należy do funkcji, musi być przesunięte o jednakowy poziom, najczęściej cztery spacje:
def show_test_started():
print("Test rozpoczęty")
print("Przygotowuję dane")
Natomiast ten zapis jest błędny, bo ciało funkcji nie ma wcięcia:
# Błędnie
def show_test_started():
print("Test rozpoczęty")
Nazwy funkcji pisz zwykle małymi literami, używając podkreśleń między wyrazami — jest to konwencja snake_case.
def create_test_user():
print("Tworzę użytkownika testowego")
Dobra nazwa opisuje czynność: create_test_user, build_payload, check_status_code. W testach szczególnie warto unikać nazw ogólnych, takich jak process_data() czy do_test().
Obejrzyj początkową część materiału, aby zobaczyć definicję i wywołanie funkcji w działającym programie.
Materiał „9. Kurs Python 3 – funkcje” z kanału Kanał o Wszystkim pokazuje podstawową składnię funkcji i sposób przekazywania danych do jej wnętrza.
W części definicja funkcji zwróć uwagę na rolę def, nawiasów, dwukropka i wcięcia oraz na to, dlaczego definicja nie uruchamia kodu. Następnie obejrzyj parametry funkcji, obserwując, jak wartości podane przy wywołaniu stają się dostępne wewnątrz ciała funkcji.
Parametry i argumenty: dwa podobne, lecz różne pojęcia
Rozróżnienie jest proste, ale bardzo przydatne podczas czytania błędów i dokumentacji:
- parametr to nazwa w definicji funkcji;
- argument to konkretna wartość przekazana przy jej wywołaniu.
def greet_user(name):
print(f"Cześć, {name}!")
W tym przykładzie name jest parametrem. W wywołaniu:
greet_user("Ala")
napis "Ala" jest argumentem.

Parametr działa wewnątrz funkcji jak lokalna zmienna:
def show_status(status_code):
print(f"Otrzymany kod: {status_code}")
show_status(201)
show_status(404)
Ta sama funkcja wykonuje ten sam rodzaj pracy, ale na różnych danych. To właśnie daje jej wartość: nie kopiujesz logiki dla każdego przypadku.
W automatyzacji testów może to wyglądać tak:
def show_response_summary(status_code, endpoint):
print(f"Endpoint: {endpoint}")
print(f"Status: {status_code}")
show_response_summary(201, "/users")
show_response_summary(404, "/users/999")
Argumenty pozycyjne: kolejność ma znaczenie
Gdy przekazujesz argumenty bez nazw, Python przypisuje je według kolejności parametrów w definicji. Są to argumenty pozycyjne.
def build_user(email, role):
return {
"email": email,
"role": role,
}
user = build_user("ania@example.test", "viewer")
Python przypisuje tu wartości następująco:
email = "ania@example.test"
role = "viewer"
Jeżeli odwrócisz argumenty, funkcja nadal formalnie się wykona, ale dane będą semantycznie błędne:
# Kod zadziała, ale wartości trafią do niewłaściwych pól.
user = build_user("viewer", "ania@example.test")
To jeden z powodów, dla których warto dobierać kolejność parametrów świadomie. Najpierw zwykle umieszcza się dane obowiązkowe i najważniejsze.
Brak wymaganego argumentu powoduje TypeError:
# build_user("ania@example.test")
# TypeError: build_user() missing 1 required positional argument: 'role'
Nie jest to błąd testowanej aplikacji. To błąd w sposobie wywołania własnej funkcji: jej definicja wymagała dwóch danych, a przekazano jedną.
Dla funkcji o jednym lub dwóch bardzo oczywistych argumentach zapis pozycyjny jest zwięzły i czytelny:
assert_response_status(201, 201)
Przy większej liczbie argumentów lub wartościach tego samego typu łatwo jednak pomylić kolejność. Wtedy użyj argumentów nazwanych.
Argumenty nazwane: kod, który sam wyjaśnia dane
Argument nazwany podajesz w formie nazwa_parametru=wartość.
def build_user(email, role, active):
return {
"email": email,
"role": role,
"active": active,
}
user = build_user(
email="ania@example.test",
role="viewer",
active=True,
)
Ten zapis jest nieco dłuższy, ale wyraźnie pokazuje znaczenie każdej wartości. Możesz też zmienić kolejność argumentów nazwanych:
user = build_user(
active=True,
email="ania@example.test",
role="viewer",
)
Rezultat będzie taki sam, ponieważ Python dopasowuje wartości po nazwach, a nie po miejscach.
Argumenty nazwane są szczególnie przydatne w kodzie testowym, gdy funkcja przyjmuje kilka parametrów albo kiedy wartości są trudne do odróżnienia na pierwszy rzut oka:
def build_request_options(timeout_seconds, follow_redirects, verify_ssl):
return {
"timeout_seconds": timeout_seconds,
"follow_redirects": follow_redirects,
"verify_ssl": verify_ssl,
}
options = build_request_options(
timeout_seconds=10,
follow_redirects=False,
verify_ssl=True,
)
Porównaj to z wersją pozycyjną:
options = build_request_options(10, False, True)
Druga wersja jest poprawna, ale po kilku dniach trudno pamiętać, co dokładnie oznaczają False i True.
Łączenie argumentów pozycyjnych i nazwanych
Python pozwala użyć obu stylów w jednym wywołaniu:
def create_account(email, role, active):
return {
"email": email,
"role": role,
"active": active,
}
account = create_account(
"ania@example.test",
role="viewer",
active=True,
)
Obowiązuje jednak ważna reguła:
Argumenty pozycyjne muszą wystąpić przed nazwanymi.
# Błędnie:
# create_account(email="ania@example.test", "viewer", True)
Po rozpoczęciu przekazywania argumentów po nazwie podawaj już kolejne również po nazwie. Dzięki temu wywołanie pozostaje jednoznaczne.
Parametry domyślne: rozsądny standard, możliwość zmiany
Nie każdy parametr musi być wymagany. Parametr domyślny ma wartość zapisaną już w definicji funkcji. Gdy wywołanie nie poda argumentu, Python użyje tej wartości.
def build_user(email, role="viewer"):
return {
"email": email,
"role": role,
}
Teraz możesz wywołać funkcję na dwa sposoby:
basic_user = build_user("ania@example.test")
admin_user = build_user(
"piotr@example.test",
role="admin",
)
W pierwszym przypadku role przyjmuje domyślnie "viewer". W drugim wartość domyślna jest zastąpiona przez "admin".
To dobrze pasuje do automatyzacji. Jeżeli większość danych testowych opisuje aktywnego użytkownika, możesz zapisać tę regułę w funkcji:
def build_user(email, role="viewer", active=True):
return {
"email": email,
"role": role,
"active": active,
}
regular_user = build_user("ania@example.test")
blocked_admin = build_user(
"admin@example.test",
role="admin",
active=False,
)
Parametry domyślne powinny znajdować się po parametrach wymaganych:
# Poprawnie
def build_user(email, role="viewer"):
return {"email": email, "role": role}
# Błędnie — Python nie wie, jak interpretować wywołanie z jednym argumentem.
# def build_user(role="viewer", email):
# return {"email": email, "role": role}
Gdyby taki zapis był dozwolony, build_user("ania@example.test") nie mówiłoby jasno, czy przekazana wartość ma trafić do role, czy do email.
Poniższy fragment materiału utrwala parametry domyślne oraz przejście do zwracania wartości.
Kontynuuj materiał „9. Kurs Python 3 – funkcje”, aby zobaczyć parametry opcjonalne i praktyczną różnicę między print() a return.
Obejrzyj wartości domyślne, zwłaszcza zasadę, że parametry z domyślną wartością stoją na końcu definicji. Następnie przejdź do return w praktyce; skup się na tym, jak wynik funkcji można przypisać do zmiennej i dlaczego instrukcje po return nie zostaną wykonane.
print() a return: pokazanie wyniku to nie to samo co jego zwrócenie
To jedno z najważniejszych rozróżnień w Pythonie.
Funkcja z print() wyświetla informację człowiekowi:
def print_total(items_count):
print(f"Liczba elementów: {items_count}")
result = print_total(3)
print(result)
Wynik będzie następujący:
Liczba elementów: 3
None
Dlaczego result zawiera None? Ponieważ funkcja niczego nie zwróciła. print() jedynie wyświetlił tekst w konsoli.
Jeśli rezultat ma zostać wykorzystany w dalszym kodzie — zapisany, porównany asercją, przekazany do innej funkcji — użyj return:
def calculate_total(price, quantity):
return price * quantity
total = calculate_total(19.99, 3)
print(total)
# 59.97
Teraz total zawiera liczbę, z którą można dalej pracować:
assert calculate_total(19.99, 3) == 59.97
W testach z reguły potrzebujesz właśnie wartości zwracanych, a nie funkcji, które tylko coś wypisują. Asercja porównuje dane, nie komunikaty w terminalu.
return kończy wykonywanie funkcji
Po wykonaniu return funkcja natychmiast kończy działanie:
def get_access_label(is_active):
if is_active:
return "access granted"
return "access denied"
Dla True zostanie zwrócone "access granted" i druga instrukcja return nie zostanie już osiągnięta. Dla False warunek zostanie pominięty, a funkcja zwróci "access denied".
Kod zapisany po bezwarunkowym return jest nieosiągalny:
def get_status():
return "ok"
print("Ten tekst nigdy się nie wyświetli")
W prostych funkcjach return zwykle znajduje się na końcu. W funkcjach z warunkami może wystąpić w kilku gałęziach, ale każda możliwa ścieżka powinna mieć jasny rezultat.
Budujemy użyteczną funkcję dla danych testowych
Połączmy elementy poznane dzisiaj oraz struktury danych z poprzedniej lekcji. Chcemy tworzyć dane użytkownika do żądania API.
def build_user_payload(email, role="viewer", active=True):
return {
"email": email,
"role": role,
"active": active,
}
Definicja mówi, że:
emailjest wymagany;rolejest opcjonalny i domyślnie ma wartość"viewer";activejest opcjonalny i domyślnie ma wartośćTrue;- funkcja zwraca słownik, który później będzie można przekazać do żądania HTTP.
Możemy stworzyć kilka wariantów danych bez kopiowania całego słownika:
new_viewer = build_user_payload("ania@example.test")
new_admin = build_user_payload(
email="piotr@example.test",
role="admin",
)
inactive_user = build_user_payload(
email="ola@example.test",
active=False,
)
Po wywołaniu new_admin będzie mieć wartość:
{
"email": "piotr@example.test",
"role": "admin",
"active": True,
}
Najważniejsze jest to, że funkcja nie wysyła jeszcze żadnego żądania API. Jej pojedyncza odpowiedzialność to przygotowanie danych. W przyszłych modułach wykorzystasz takie funkcje jako małe, przewidywalne elementy kodu testowego.
Możesz od razu sprawdzać jej wynik zwykłą asercją:
payload = build_user_payload(
email="tester@example.test",
role="editor",
)
assert payload["email"] == "tester@example.test"
assert payload["role"] == "editor"
assert payload["active"] is True
To dobry wzorzec myślenia: funkcja przyjmuje dane wejściowe, zwraca konkretny wynik, a test porównuje wynik z oczekiwaniem.
Typowe błędy i jak je rozpoznać
| Sytuacja | Przyczyna | Poprawa |
|---|---|---|
TypeError: missing ... required positional argument | Brakuje wymaganego argumentu. | Podaj wartość albo zdefiniuj uzasadnioną wartość domyślną. |
Wynik funkcji to None | Funkcja używa print(), ale nie ma return. | Zwróć potrzebną wartość przez return. |
SyntaxError: non-default argument follows default argument | Parametr wymagany został zapisany po domyślnym. | Najpierw parametry wymagane, potem domyślne. |
| Dane trafiają do niewłaściwych pól | Pomyłka w kolejności argumentów pozycyjnych. | Użyj argumentów nazwanych. |
Kod funkcji wykonuje się od razu po def | To błędne założenie — definicja tylko tworzy funkcję. | Wywołaj ją przez nazwa_funkcji(...). |
IndentationError | Ciało funkcji ma nieprawidłowe wcięcie. | Wcięcie całego ciała ustaw konsekwentnie na cztery spacje. |
W trakcie nauki warto często uruchamiać małe fragmenty kodu w swoim projekcie z .venv. Błędy Pythona bywają bardzo konkretne: przeczytaj komunikat od końca i sprawdź nazwę funkcji oraz liczbę podanych argumentów.
Zasady, które warto stosować od teraz
Dobra funkcja pomocnicza w testach zwykle:
- ma nazwę opisującą działanie;
- wykonuje jedną, spójną rzecz;
- przyjmuje tylko dane, których rzeczywiście potrzebuje;
- używa wartości domyślnych wyłącznie dla sensownych standardów;
- zwraca dane, jeśli dalszy kod ma je sprawdzić lub przetworzyć;
- nie polega na ukrytej kolejności wielu podobnych argumentów — w takim przypadku korzysta z argumentów nazwanych.
Krótka ściąga składni:
def function_name(required_value, optional_value="default"):
result = required_value + optional_value
return result
value = function_name("A", optional_value="B")
W definicji required_value i optional_value są parametrami. W wywołaniu "A" oraz "B" są argumentami. Argument "A" przekazano pozycyjnie, a "B" nazwano wyraźnie przez optional_value=.
Najważniejsze wnioski:
- Funkcję definiujesz przez
def, nazwę, nawiasy, dwukropek i wcięte ciało. - Parametry są zapisane w definicji, a argumenty przekazujesz przy wywołaniu.
- Argumenty pozycyjne zależą od kolejności; argumenty nazwane są dopasowywane po nazwie i zwiększają czytelność.
- Parametry domyślne czynią argument opcjonalnym, ale muszą występować po wymaganych parametrach.
print()pokazuje wartość, natomiastreturnoddaje ją kodowi wywołującemu.- Instrukcja
returnkończy wykonywanie funkcji. - Funkcje zwracające dane są szczególnie użyteczne w automatyzacji, ponieważ ich wyniki można bezpośrednio sprawdzać asercjami.
W następnej lekcji dodasz do funkcji adnotacje typów. Dzięki nim podpis funkcji będzie informował nie tylko o nazwach parametrów, lecz także o oczekiwanych typach danych i typie zwracanego wyniku.
Can't find a good explanation? Sign up and we'll make it for you
Sign up