Testing and Debugging

A program that runs is not necessarily a program that works. A test checks that your code gives the right answer. Debugging is what you do when it does not. Now that you can write functions, you have something worth testing.

What a test is

A test calls your function with an input you chose and compares the result with the answer you worked out by hand. Python's assert statement does the comparison. It is followed by a condition. If the condition is True, nothing happens. If it is False, the program stops with an AssertionError.

Run this cell, then change one of the expected values and run it again.

You can add a message after a comma. It is shown when the assert fails, so say what you expected:

assert is_even(3) == False, "3 is odd, so is_even(3) should be False"

The project solutions end with tests written with a tool called unittest, which you will meet later. The idea is the same: call the code, check the result.

Choose what to test

Decide the expected answer before you run anything. Otherwise it is easy to glance at the output and decide it looks fine. Use the kinds of input listed in Problem Solving. For is_even that means a typical value (4), the other answer (3), zero, and a negative number (-2).

Put tests in a function

Group the asserts for one function into a test function. Name it test_ followed by the name of the function it checks, then call it.

In VS Code, put the function and its tests in one file and run it with the Run triangle, as in Getting started. If the file prints "All tests passed", every assert held. If one fails, the terminal shows something like this:

Traceback (most recent call last):
  File "even.py", line 14, in <module>
    test_is_even()
  File "even.py", line 9, in test_is_even
    assert is_even(0) == True, "0 is even"
AssertionError: 0 is even

Read it from the bottom. The last line gives the error type and your message, which says which check failed. The line above it points to the assert. The lines above that show which functions were running, the same stack trace you met in Functions. Your line numbers will differ from this example.

Debugging

When a test fails, work in this order:

  1. Shrink the problem. Find the smallest input that still fails.
  2. Find the first wrong value. Follow the data through the code until a value is not what you expected. The bug is at or just before that point.
  3. Fix one thing, then run all the tests again.

Step 2 is Code Tracing, with the computer doing the work. Two tools help.

Print debugging

Print the values you are unsure about. This function should take 10 percent off a price, but the answer is negative. Run it and see where the numbers go wrong.

The subtotal is right and the total is not, so the bug is in the line between the two prints. The discount is 10, but the formula needs a fraction such as 0.1. The fix is to divide by 100 in the formula: discount / 100 * subtotal. Delete your debugging prints when you are done.

The VS Code debugger

A debugger pauses your program so you can look at it. Try it on any file with a function in it.

  1. Click in the margin just left of a line number. A red dot appears. This is a breakpoint. The program will pause before it runs that line.
  2. Choose Run, Start Debugging (or press F5). If VS Code asks, pick Python File.
  3. The program runs until it reaches the breakpoint and stops. The Variables panel on the left shows the current value of every variable.
  4. Use the buttons at the top, or these keys, to move:
Key Button What it does
F10 Step Over Run this line and stop at the next one
F11 Step Into If the line calls one of your functions, go inside it
Shift+F11 Step Out Finish the current function and go back to the caller
F5 Continue Run until the next breakpoint, or until the program ends
Shift+F5 Stop End the debugging session

On a Mac laptop you may need to hold Fn with the function keys.

Step through the code and watch the Variables panel after each line. Stop when a value is wrong. That line, or the one before it, holds the bug. You can type any expression into the Watch panel to follow it as you step, and the Call Stack panel shows which function called which.

Quizzes

  1. The asserts below fail. Run the code and read the message, then fix the function so every assert passes.
Hint 1

// gives the whole-number part of a division. Which operator gives the remainder?

  1. A year is a leap year if it is divisible by 4, except that years divisible by 100 are not leap years unless they are also divisible by 400. The function is_leap_year below follows that rule.

Write a function named test_is_leap_year that checks it with asserts, then call it. Your tests are good if they would catch a wrong version of the function. This page runs yours against three wrong versions, and each one must make an assert fail.

Hint 1

Try an ordinary leap year, an ordinary non-leap year, a year divisible by 100 and a year divisible by 400.

  1. sum_to(n) should add the whole numbers from 1 to n, so sum_to(4) is 1 + 2 + 3 + 4 = 10. It prints 6. Add a print inside the loop to see which values i takes, then fix the function. Remove your debugging prints afterward.
Hint 1

range(4) gives 0, 1, 2, 3. You need 1, 2, 3, 4.