As soon as you have more than one test, repetition shows up: each one logs in, gets a token, builds headers. Copying those three lines into ten tests means changing them in ten places later. A function solves exactly that: the code is written once and called by name.
What a function looks like
live example
def greet():
print("Hello")
greet() # the call: now it prints
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Free week →
def means "I am defining a function", then the name, brackets and a colon. The
body is indented. The definition alone does nothing: until the function is
called, nothing inside it happens.
Parameters: same work, different data
live example
def greet(name):
print(f"Hello, {name}")
greet("Anna")
greet("Ivan")
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Free week →
The name in brackets is a parameter: a variable that appears inside the function with the value passed at the call.
Returning a value
Printing from inside a function is almost always wrong: the result is needed in
the code, not on the screen. That is what return is for:
live example
def cost(price, quantity):
return price * quantity
total = cost(4990, 2)
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Free week →
return ends the function immediately and hands the value out. Anything written
after it does not run.
A function can also answer yes/no — those are especially useful in checks:
def is_paid(order):
return order["status"] == "PAID"
if is_paid(order):
...
Default values
def find_orders(orders, status="PAID"):
return [o for o in orders if o["status"] == status]
find_orders(orders) # looks for PAID
find_orders(orders, "CANCELLED") # looks for cancelled
One trap that hurts when you meet it: a default value must not be a list or a dict.
live example
def bad(item, into=[]): # do not do this
into.append(item)
return into
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Free week →
Such a list is created once for the whole program and accumulates values between
calls. The correct way is into=None and if into is None: into = [] inside.
Why a tester needs this
Test setup. Login, token, headers in one function:
live example
def login(client):
response = client.post("/auth/login", json={"email": "anna@example.com", "password": "secret123"})
return {"Authorization": "Bearer " + response.json()["token"]}
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Free week →
One line per test instead of three — and when the login changes, one place to fix. In real pytest such a function becomes a fixture; that step is small.
Your own check. A repeating condition is extracted and named by meaning:
live example
def total_adds_up(order):
computed = sum(i["qty"] * i["price"] for i in order["items"])
return computed == order["total"]
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Free week →
The reader sees if not total_adds_up(order) and understands the intent without
parsing arithmetic.
Parsing a response. Pulling what you need out of a nested response is a function too: the test stays about checking, not about navigating dicts.
Naming
A function name is a verb or a question: login, find_orders, is_paid,
total_adds_up. If the name grows into do_everything_and_check, the function
does too much and should be split.
What to solve
In short
def name(params):defines a function; nothing happens until it is called.returnhands the result out and ends the function; printing from inside is almost always a mistake.- A default value makes a parameter optional; it must not be a list or a dict.
- In tests, functions cover three jobs: setup, your own check, response parsing.
- The name is a verb or a question; an overly long name means the function does too much.
What to read next
- assert and errors — how a check fails readably.
- Working with data — count, compare, find the difference.