Skip to content
Aabha AI Academy
Skip to reading

Python · Module 6

Debugging and tests

Outcome: reproduce a defect, isolate its cause and keep a regression test. Prerequisite: Module 5. Profile: Python basics. Time: about 2 hours.

Debug with a question

Debugging is a process of narrowing explanations. Start with an exact input, expected output and actual output or error. “It does not work” gives you nowhere to begin. “For [10, 0, 20], I expected two active sessions but got one” points toward counting behavior.

Read a traceback from its final line first: it names the exception and message. Then inspect the referenced line in your own code, and the calls leading to it. The line that fails may expose a value created incorrectly earlier. Printing repr(value), type(value) and a small intermediate result can reveal whitespace, the wrong type or an accumulator reset.

Reduce the failing example while preserving the failure. A three-item list is easier to trace than a thousand rows. Change one relevant thing, rerun the reproducer and then rerun the checks that used to pass. If you make five unrelated changes, you lose evidence about which one fixed the problem.

Tests are executable expectations

A test gives known input and checks an observable result. An ordinary case is useful, but boundaries and failure cases often reveal more: an empty list, one item, all zeros and an invalid value. Choose those cases from the function's contract, not simply from the values you happen to have today.

An assertion fails when its condition is false. It is convenient for exercises, but Python can disable assertions with optimization flags. Do not use assert as the only validation for user input or a production safety rule. Use explicit checks and exceptions for the contract, and a testing framework for repeatable test execution.

Worked example

# Profile: Python basics. These checks run in the normal interpreter.
import math


def mean_minutes(values):
    """Return the arithmetic mean; empty input is an error."""
    if not values:
        raise ValueError("at least one value is required")
    return sum(values) / len(values)


assert math.isclose(mean_minutes([10, 20, 30]), 20.0)
assert math.isclose(mean_minutes([12]), 12.0)
assert math.isclose(mean_minutes([0, 0]), 0.0)
try:
    mean_minutes([])
except ValueError:
    print("Empty-input check passed")
else:
    raise AssertionError("empty input should raise ValueError")
print("Three numeric checks passed")

Download this worked script

Expected messages are Empty-input check passed and Three numeric checks passed. math.isclose handles tiny representation differences in approximate numeric values. Exact integer counts and text can usually use direct equality. rel_tol is a relative allowance scaled by the magnitude of the values; abs_tol is an absolute allowance useful near zero. For these tiny deterministic arithmetic checks, use math.isclose(a, b, rel_tol=1e-9, abs_tol=1e-12). The defaults are not a measurement policy: choose tolerances from the problem. For example, math.isclose(1e-13, 0.0, rel_tol=1e-9, abs_tol=1e-12) is true because of the absolute allowance; 0.31 versus 0.3 is false with these tolerances.

For this list contract, if not values means the same as if len(values) == 0: an empty list is false in a condition and a nonempty list is true. This shorthand does not validate the type of every possible object.

The empty-input test is important. Returning 0 as the mean of no observations would look like a real measurement. Our contract raises ValueError instead. Another application might choose None, but it must state that policy and make callers handle it.

Investigate a wrong result before reading its explanation

The starter's function is meant to count strictly positive values. For [10, 0, 20], write the expected result, then run it and record the actual result. Find a shorter input that still fails. Trace count after each executed line, propose one change and explain why it should help before editing. Keep that smallest failing input as a regression test. Finally, run an empty list and record any different failure.

Use exercises/06_diagnostic_worksheet.md to capture your evidence. The diagnosis is in the separate answer guide; leave it closed until you have tried the trace. A useful hint is to compare what happens on the first and second loop iterations, without adding a new condition.

Do not change expected answers to match broken output. Change an expectation only when its paired input or agreed contract changes, and retain the old case.

Read a traceback from another failure

The following example deliberately converts the text "twenty" with int. It catches the planned error only to display the traceback and let this teaching script finish. Your own data-processing function must still decide whether to reject the record or stop.

# Profile: Python basics. This demonstration catches and prints its planned failure.
import sys
import traceback

def hours_from_text(text):
    minutes = int(text)
    return minutes / 60

raw_minutes = "twenty"
try:
    print(hours_from_text(raw_minutes))
except ValueError:
    traceback.print_exc(file=sys.stdout)
    print("The demonstration stopped the invalid conversion; no hours were produced.")

Download this worked example

The relevant traceback looks like this (directory paths shortened):

Traceback (most recent call last):
  File ".../06_traceback.py", line 11, in <module>
    print(hours_from_text(raw_minutes))
  File ".../06_traceback.py", line 6, in hours_from_text
    minutes = int(text)
ValueError: invalid literal for int() with base 10: 'twenty'

Read the output in three passes:

  1. The final ValueError: invalid literal for int() with base 10: 'twenty' states the failed conversion and the actual value.
  2. The frame containing minutes = int(text) identifies the failing operation. Division by 60 has not happened yet.
  3. The earlier call hours_from_text(raw_minutes) leads back to the assignment that supplied "twenty". Inspect that input rather than changing the division.

File paths and line numbers depend on where you run the downloaded script; the call chain and failed value are the useful evidence. For a follow-up, change only the source to "20minutes": identify the failed operation, actual input and likely upstream field to inspect. Then try "20" and explain why it succeeds. Record the explanation before opening the answers.

A repeatable local test command

This package uses Python's standard-library unittest so tests do not require another package. From the course root, run python -m unittest discover -s tests -v with your course virtual environment's Python. The suite tests reference solutions, the dataset policy and regressions. A green reference suite does not grade your edited starter automatically. The readiness checker explicitly imports your own readiness functions.

A framework reports each test and groups failures. Read the first relevant failure, fix the cause, then rerun the full suite. Passing tests are evidence for the cases checked, not proof that the program works for every possible input, environment or dataset.

Practice

  • 6A: fix count_active in exercises/06_debug_tests.py. First demonstrate the bug with [10, 0, 20], then test empty input, [0] and [7].
  • 6B: implement mean_minutes with the empty-input error. Test a single value, all zeros and [10, 20, 30].
  • 6C: use approximate comparison to check 0.1 + 0.2 against 0.3. Add an explicit negative test showing that 0.31 should not be considered close at a tight tolerance. Explain what each test guards.

Exit check

What is the smallest input that exposes the counting bug? How is a regression test different from printing the answer once? Your tests pass for [10, 20, 30] and [12]. Which behavior is still untested? Choose a new input from the contract, predict its result and explain which mistake that test could catch.

Further reading

Read the separate answers and explanations

Reading progress

11 lessons remain open to guests. Marking a lesson read records reading only; it does not award assessment credit or a certificate.

Device reading marks require browser storage. Reading is always available.

Sign in or create an account to save separate account progress. Your current page is kept.