Module 2: Operators & Control Flow

Python for Testers · Module 2

Operators & Control Flow

So far your programs run straight down the page, top to bottom, doing the same thing every time. Real tests do not work that way—they make decisions (“if the banner says success, pass; otherwise fail”) and they repeat work (“try the login with each of these ten accounts”). This module gives your code a brain and a loop. You will learn the operators that compute and compare values, the if/elif/else statements that branch, and the for and while loops that repeat. These are the control structures behind every assertion and every data-driven test you will ever write.

Learning objectives

By the end of this lesson you will be able to:

  • Use arithmetic, comparison, logical, and assignment operators correctly.
  • Write if, elif, and else branches to make decisions.
  • Explain truthiness and which values Python treats as False.
  • Loop over a range of numbers and over items in a collection with for.
  • Repeat work with while until a condition changes.
  • Control loops with break, continue, and pass, and nest loops.
  • Write a compact decision with a ternary (conditional) expression.

Arithmetic operators

These do maths on numbers. Most are obvious, but three deserve attention.

Operator Meaning Example Result
+ - * Add, subtract, multiply 3 * 4 12
/ True division (always a float) 7 / 2 3.5
// Floor division (drops the remainder) 7 // 2 3
% Modulo (the remainder) 7 % 2 1
** Exponent (power) 2 ** 3 8

The modulo operator % returns what is left over after division, and it is surprisingly useful in testing. number % 2 == 0 is the standard way to ask “is this number even?”—handy when you want to apply a step to every second record, or alternate between two test accounts.

total = 10 + 5          # 15
average = total / 2     # 7.5  (note: a float, not 7)
remainder = 17 % 5      # 2
is_even = 8 % 2 == 0    # True

Comparison operators

These compare two values and always produce a Boolean (True or False). They are the heart of every assertion: a test passes when the actual value compares as expected.

Operator Meaning Example (True)
== Equal to status == 200
!= Not equal to status != 500
> < Greater / less than price < 100
>= <= Greater / less than or equal count >= 1
actual_status = 200
print(actual_status == 200)   # True  -- test passes
print(actual_status != 200)   # False
print(5 < 3)                   # False
print(5 >= 5)                  # True

The single most common beginner mistake is confusing = (assignment—“put this value in this variable”) with == (comparison—“are these two things equal?”). = changes a variable; == asks a question and gives back True or False.

Logical operators

To combine conditions, use and, or, and not—written as plain words, not symbols.

  • and — True only when both sides are true.
  • or — True when at least one side is true.
  • not — flips a Boolean: not True is False.
logged_in = True
is_admin = False

print(logged_in and is_admin)   # False (both must be true)
print(logged_in or is_admin)    # True  (one is enough)
print(not is_admin)             # True

# A realistic check: a valid test run
status = 200
body_ok = True
print(status == 200 and body_ok)   # True -- response is fully healthy

Assignment operators

Beyond plain =, Python offers shortcuts that update a variable using its own current value. count += 1 means “take count, add 1, store it back”—the everyday way to increment a counter.

passed = 0
passed += 1      # same as: passed = passed + 1  -> 1
passed += 1      # 2

retries = 3
retries -= 1     # 2
timeout = 10
timeout *= 2     # 20

Making decisions: if / elif / else

An if statement runs a block only when a condition is true. Add elif (“else if”) to test further conditions, and else as a catch-all when none matched. Note the two non-negotiable pieces of syntax: a colon at the end of each condition line, and indentation of the block beneath it.

status = 404

if status == 200:
    print("OK -- test passes")
elif status == 404:
    print("Not Found -- resource missing")
elif status >= 500:
    print("Server error -- investigate")
else:
    print("Unexpected status:", status)

Python checks the conditions top to bottom and runs the block for the first one that is true, then skips the rest. The elif and else parts are optional—a bare if on its own is perfectly valid. This structure is exactly how a test classifies an outcome: pass, known failure, or something unexpected.

This is the shape of an assertion

Before you learn a real assertion library, you can hand-roll the logic a test framework automates for you:

expected_title = "Dashboard"
actual_title = "Dashboard"     # imagine this came from the page

if actual_title == expected_title:
    print("PASS: title matched")
else:
    print("FAIL: expected", expected_title, "but got", actual_title)

Every assertion you will ever write—assert actual == expected in pytest, for example—is this pattern underneath: compare, then branch on the result. Understanding the if here means the frameworks later hold no surprises.

Truthiness

An if does not strictly need a True/False—it accepts any value and decides whether it is “truthy” or “falsy”. The following values are falsy (treated as False): the number 0, 0.0, the empty string "", the empty list [], the empty dict {}, and the special value None. Almost everything else is truthy.

results = []           # an empty list

if results:
    print("We have results to report")
else:
    print("No results collected yet")   # this runs -- empty list is falsy

name = ""
if not name:
    print("Name is missing")            # this runs -- empty string is falsy

This lets you write natural checks like “if the list of errors is not empty…” as simply if errors:. It is idiomatic Python and you will read it everywhere.

Do not compare to True, and mind the chained logic

Because of truthiness, writing if logged_in == True: is redundant—just write if logged_in:. Worse, comparing to True can bite you: a value like "yes" is truthy, so if "yes": runs, but "yes" == True is False. So if "yes" == True: would not run its block, even though the string is truthy. Trust truthiness: use if value: to mean “if value is present/non-empty/non-zero,” and reserve == for comparing actual contents. This exact confusion causes tests that silently never execute their check and therefore never fail—the most dangerous kind of broken test.

The for loop

A for loop repeats a block once for each item in a sequence. It is the workhorse of data-driven testing: run the same steps for every row of test data.

Looping with range()

When you want to repeat something a set number of times, use range(). range(5) produces the numbers 0, 1, 2, 3, 4—it starts at 0 and stops before the number you give it.

for i in range(3):
    print("Attempt", i)

# Attempt 0
# Attempt 1
# Attempt 2

range can also take a start and a step: range(1, 4) gives 1, 2, 3, and range(0, 10, 2) gives 0, 2, 4, 6, 8. The variable i is just a name—call it whatever reads best.

Looping over iterables

More often you loop directly over a collection—a list of usernames, say—without caring about index numbers. This is cleaner and more Pythonic than using indexes.

browsers = ["Chrome", "Firefox", "Edge"]

for browser in browsers:
    print("Running suite on", browser)

# Running suite on Chrome
# Running suite on Firefox
# Running suite on Edge

Read it aloud: “for each browser in browsers.” Each time round, browser holds the next item. This is precisely how you would run the same test against several browsers or feed a list of test inputs through one check. You will meet lists in detail in Module 4.

The while loop

A while loop repeats as long as a condition stays true. Use it when you do not know in advance how many times you need to loop—for example, polling a page until something appears, or retrying until success.

attempts = 0
max_attempts = 3
connected = False

while not connected and attempts < max_attempts:
    attempts += 1
    print("Connection attempt", attempts)
    # pretend we succeed on the third try:
    if attempts == 3:
        connected = True

print("Connected!" if connected else "Gave up.")

The condition is checked before each pass. The golden rule of while loops: make sure something inside the loop eventually makes the condition false—here, attempts += 1. Forget it and the loop never ends.

Tutor tip: the infinite-loop escape hatch

Sooner or later you will write a while loop whose condition never becomes false—an infinite loop—and your program will hang, printing forever or sitting silent. Do not panic and do not close the whole editor. In the terminal where it is running, press Ctrl+C. That sends an interrupt that stops the running program and hands you back the prompt. It is the universal “stop this now” for command-line programs, and you will use it often.

break, continue, and pass

Three small keywords give you fine control inside loops:

  • break — exit the loop immediately, skipping any remaining items.
  • continue — skip the rest of the current pass and jump to the next item.
  • pass — do nothing; a placeholder where Python’s syntax requires a statement but you have none yet.
accounts = ["valid1", "", "valid2", "STOP", "valid3"]

for account in accounts:
    if account == "":
        continue          # skip blank entries, move to the next
    if account == "STOP":
        break             # abort the whole loop here
    print("Testing account:", account)

# Testing account: valid1
# Testing account: valid2

pass is different—it is a “to do” marker. When you are sketching out code and want an empty branch or function body that is syntactically legal, pass fills the gap:

if status == 418:
    pass   # TODO: decide how to handle "I'm a teapot" later

Nested loops

A loop can live inside another loop. The inner loop runs completely for every single pass of the outer loop—useful for testing every combination, such as each browser against each environment.

browsers = ["Chrome", "Firefox"]
environments = ["staging", "prod"]

for browser in browsers:
    for env in environments:
        print("Run:", browser, "on", env)

# Run: Chrome on staging
# Run: Chrome on prod
# Run: Firefox on staging
# Run: Firefox on prod

Two browsers times two environments gives four combinations—the loops produce every pairing automatically. Keep nesting shallow, though: three or four levels deep becomes hard to read, and there is usually a cleaner approach.

The ternary (conditional) expression

For a simple either/or choice you can condense an if/else into one line. The form reads almost like English: value_if_true if condition else value_if_false.

passed = True
result = "PASS" if passed else "FAIL"
print(result)   # PASS

count = 0
message = "No tests" if count == 0 else "Some tests ran"
print(message)  # No tests

You already saw this earlier in the while example ("Connected!" if connected else "Gave up."). Use it for short, clear choices—assigning a label, picking a message. For anything with multiple branches or real logic, a full if/elif/else stays more readable.

Hands-on exercise

Create grading.py and combine operators with control flow into a small test-result reporter:

  1. Make a list of integer test scores, e.g. scores = [100, 0, 75, 90, 55].
  2. Loop over the scores with a for loop.
  3. Inside the loop, use if/elif/else to print "PASS" for scores >= 60 and "FAIL" otherwise—plus the score.
  4. Use continue to skip any score of 0 (treat it as “not run”) and print a note instead.
  5. Keep a running count of passes using +=, and print the total at the end.
  6. After the loop, use a ternary expression to print "Suite healthy" if every non-skipped test passed, else "Needs attention".
  7. Bonus: add a while loop that simulates retrying a flaky test up to three times, stopping early with break when it “passes.”

Knowledge check

  1. What is the difference between / and //, and what does % return?
  2. Why is confusing = and == such a common bug?
  3. Which values does Python treat as falsy? Name at least four.
  4. What numbers does range(1, 6) produce?
  5. When would you reach for a while loop instead of a for loop?
  6. Explain the difference between break and continue.
  7. Rewrite this as a ternary: if ok: label = "up" else: label = "down".
Show answers
  1. / is true division and always gives a float (7 / 2 is 3.5); // is floor division and drops the remainder (7 // 2 is 3); % returns the remainder (7 % 2 is 1).
  2. = assigns a value to a variable while == compares two values; using one where you meant the other either changes data unexpectedly or always returns the wrong Boolean.
  3. Any four of: 0, 0.0, "" (empty string), [] (empty list), {} (empty dict), None.
  4. 1, 2, 3, 4, 5 (it stops before 6).
  5. When you do not know in advance how many iterations are needed—e.g. retrying or polling until a condition changes.
  6. break exits the loop entirely; continue skips the rest of the current iteration and moves to the next item.
  7. label = "up" if ok else "down"

Key takeaways

  • Operators compute and compare: arithmetic (+ - * / // % **), comparison (== != > < >= <=), logical (and or not), and assignment shortcuts (+= -=).
  • if/elif/else branch your code—watch the colon and the indentation—and this is exactly the logic behind every assertion.
  • Truthiness lets you write if errors:; remember that 0, "", [], {}, and None are falsy.
  • for loops repeat over range() or over a collection; while loops repeat until a condition changes—guard against infinite loops.
  • break, continue, and pass fine-tune loops; nested loops cover combinations; a ternary expresses a simple either/or in one line.

Go deeper

A few idioms turn clunky control flow into clean, testable code:

  • Chain your comparisons. Python lets you write 0 <= score <= 100 directly, instead of score >= 0 and score <= 100. It reads like maths and is a tidy way to assert a value sits inside an expected range.
  • and/or return a value, not just a Boolean. or hands back the first truthy operand, so env = chosen or "staging" falls back to "staging" when chosen is empty. This short-circuiting also means the right-hand side is skipped entirely when the left already decides the result—handy for guarding obj and obj.value.

Loops have an often-missed else clause that runs only if the loop finished without hitting break—perfect for “searched everything and found nothing”:

for user in users:
    if user == "admin":
        print("Admin found")
        break
else:
    print("No admin in the list")   # runs only if the loop never broke

And from Python 3.10 the match statement replaces long if/elif ladders with structural pattern matching—a cleaner way to classify something like an HTTP status:

match status:
    case 200 | 201:
        print("OK")
    case 404:
        print("Not found")
    case code if code >= 500:
        print("Server error", code)
    case _:
        print("Unexpected")

Save to list

Scroll to Top