Lesson 20 of 26 · about 16 min
Testing and debugging
Testing is trying your code on carefully chosen inputs to find bugs. Debugging is the detective work of finding why a bug happens, one experiment at a time.
🧭 What you need first: When things go wrong
A bridge engineer doesn’t wait for traffic to find out whether a bridge holds. They test it: normal loads, a heavy truck, a full traffic jam, the edges near the supports. Testing is trying to break your own work before someone else does.
When something does break, you become a detective. A good detective doesn’t accuse random suspects. They gather clues, form a theory (“the butler had the key”), test it, and only then make the arrest. Debugging is detective work: each experiment narrows down where the bug can be hiding.
Another way: tasting while you cook
A good cook tastes the soup as they go, not only when it’s served. Running your tests after every change is tasting as you go. You catch the “too much salt” moment immediately, when it’s easy to fix.
Another way: the rubber duck
Programmers keep a rubber duck on their desk and explain their code to it, line by line. Halfway through explaining, they usually spot the bug. Saying exactly what you think the code does reveals where it doesn’t.
Hunt the bug
This function should return the largest number in a list. It looks right, and it even works on the first example. Run it, then add your own test lines at the bottom until you find an input that breaks it.
(Spoiler, try it first: a list of only negative numbers like [-5, -2, -9] returns 0, which isn’t even in the list. Starting biggest at nums[0] fixes it.)
The real words
Testing
- A test case is an input plus its expected output. A test suite is a collection of them.
- Choose test cases deliberately:
- Typical inputs: what you expect most of the time.
- Edge cases: empty list, zero, one item, all items equal, negative numbers, very large numbers.
- Boundaries: just below, at and just above a limit (for “grade A is 90+”, test 89, 90 and 91).
- Black-box testing: tests chosen from the specification alone, without looking at the code. Glass-box testing: tests chosen by looking at the code, so every branch and loop path runs at least once.
- Automate it:
assert largest([-5, -2]) == -2fails loudly when something breaks. Python’sdoctesteven lets you put examples inside docstrings and run them as tests. Re-running all tests after every change is called regression testing.
Debugging
- Debugging is a search. The steps:
- Reproduce it: find a small input that always triggers the bug.
- Read the error message from the bottom up: what went wrong, and on which line.
- Hypothesize: “I think
biggeststarts at the wrong value.” - Experiment: add a
print()or step through with a tracer, and see if the evidence fits. - Fix one thing, then re-run all the tests.
- Three kinds of bugs: syntax errors (Python can’t read it), runtime errors (it crashes), logic errors (it runs but gives the wrong answer, the sneakiest kind).
- When stuck: explain it out loud (rubber duck), take a break, or cut the problem in half: check whether the bug happens before or after the midpoint of the code.
Write tests, then fix the bug
average(nums) has a bug: it crashes on an empty list (it should return 0). It also has a second, sneakier bug. Write at least 3 asserts that test it (including edge cases), then fix both bugs.
Choose the best tests
A function is_adult(age) should return True for 18 and over. Which set of tests is best?
What's the FIRST step in debugging?
Got it?
Finish the lesson to color it in on your map and collect its concept cards.
Did this lesson click?