← back to section

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.
  • return hands 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.