Basic

Debugging from a failure

A test went red and I started changing random lines. That is guessing. The failure already tells you the input and the wrong output, so start there and walk inward.

What you will see

============================== 2 passed in 0.01s ===============================

About 2 min of reading

Try It

Take a red test, reproduce the case alone, and bracket the bug with a print.

1

A function with an off-by-one bug. It should return a + b but returns a + b + 1.

def add(a, b): return a + b + 1

Before you run it, say out loud what it will print. Then open the answer.

What it prints
>>>
2

Reproduce the failing case alone. You see 6 where 5 is expected, confirming the bug is inside add.

add(2, 3)

Before you run it, say out loud what it will print. Then open the answer.

What it prints
6
3

Print the intermediate to bracket it. The value is wrong at the return, so the bug is the + 1.

def add(a, b): total = a + b print("total is", total) return total + 1

Before you run it, say out loud what it will print. Then open the answer.

What it prints
total is 5
4

Fix the one line and re-run the test. It goes green, which is the loop closing.

def add(a, b): return a + b pytest test_add.py

Before you run it, say out loud what it will print. Then open the answer.

What it prints
============================== 2 passed in 0.01s ===============================

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A failure hands you an input and a wrong output, you reproduce that case, then bracket the bug by printing at seams.
1 Failure names input and output
↓
2 Reproduce that case
↓
3 Print at seams
↓
4 Bracket narrows to one line
Why this works
Reproduce before you fix

Do not change code until you can re-run the failing case on command. A test is that case. Once you can make it go red on demand, you have a loop: change something, re-run, see if it went green. Guessing without that loop is how people break a second bug while fixing a first.

Bisect by printing

When you do not know where a value goes wrong, put prints at a few points and read them. The last correct print and the first wrong one bracket the bug. Move the seam inward until the bracket is one line. That one line is the fix.

Examples

The failure gives you a case

FAILED: add(2, 3) returned 6, expected 5

You now have a concrete input (2, 3) and a wrong output (6). That is a starting point, not a search space. Reproduce just that case.

Print at the seam

def add(a, b):
    total = a + b
    print("total is", total)
    return total

Put a print where you think the value goes wrong. If it prints 5 but the caller sees 6, the bug is after the return, not inside add.

Narrow the seam

print(add(2, 3))

Call the suspect function alone with the failing input. If it returns 5 on its own, the bug is in how the result is used, not in the function.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Reading the runner output
Previous Lesson
Reading the runner output
Next Lesson
Prints you can trust
Prints you can trust →