Testing your code

Introduction

A test is a small piece of code that checks another piece of code gives the right answer. Testing catches mistakes before your users (or your examiner) do, and it lets you change code with confidence.

Testing by hand is slow, and you can only test what you remember to try. Tests written as code run in a moment and repeat every check every time, so you can change a program without worrying about what you might have broken.

How it works

assert

def average(nums):
    return sum(nums) / len(nums)

assert average([2, 4]) == 3
assert average([5]) == 5
print("All tests passed")
Open in compiler

assert condition does nothing when the condition is True, and stops the program with an AssertionError when it is False. Two checks pass here, so the last line prints.

A test that fails

def is_even(n):
    return n % 2 == 1

assert is_even(4), "4 should be even"
print("not reached")
Open in compiler

is_even has a bug: it returns n % 2 == 1, which is the test for odd. The assert catches it at line 4, and the message after the comma, 4 should be even, explains what was expected. A failing test is good news: it found the bug for you.

Syntax

assert average([2, 4]) == 3
assert result == 5, "result should be 5"
assert abs(x - 0.3) < 1e-9

Example

def largest(nums):
    best = nums[0]
    for n in nums:
        if n > best:
            best = n
    return best

tests = [([3, 9, 2], 9), ([-5, -2], -2), ([7], 7)]
for nums, expected in tests:
    assert largest(nums) == expected, nums
print(len(tests), "tests passed")
Open in compiler

Good tests include the unusual inputs: negative numbers, a single item, the largest and smallest allowed values. Keeping the cases in a list makes it easy to add more. The practice problems on this site work the same way: hidden tests are mostly edge cases.

Line by line

  1. largest is the function being tested.
  2. tests holds pairs of an input and the answer expected for it.
  3. The loop unpacks each pair and calls the function.
  4. assert stops the program if an answer is wrong, and shows the input.
  5. If every check passes, the last line reports how many there were.

Expected result

3 tests passed

In the real world

Professional projects run thousands of tests each time the code changes, often automatically before anything is published. The tools have more features, such as Python's pytest, but the idea is exactly the same.

Common mistakes

  • Testing only the example from the problem statement. Add edge cases.
  • Comparing floats with ==: 0.1 + 0.2 == 0.3 is False. Use abs(a - b) < 1e-9.
  • Asserting the code against itself instead of against an answer you worked out by hand.

Best practices

  • Add a test for each bug you fix, so that it cannot return.
  • Test one idea at a time.
  • Keep test cases in a list, so that adding one is easy.

Summary

A test checks code against an answer you worked out yourself. assert stops the program when a check fails. Good tests include edge cases, and floats need a tolerance.

Practice

Write three asserts for the factorial function from the recursion lesson, including the edge case factorial(0) == 1.

Quick check

1. What does assert 2 + 2 == 4 do?
2. Which is an edge case for a function on lists?
3. What is 0.1 + 0.2 == 0.3 in Python?

Key takeaways

  • A failing test is good news, because it found a bug.
  • Test the smallest, largest and strangest inputs.
  • Work out expected answers by hand.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.