Everyone Can Learn Program
中文 Start learning
Specialty · TestingLesson 1 of 5Free to read

Your first tests · Software Testing

How do you know your code still works after you change it?

Course contents

Why you need it

Every change can break something that used to work, often far from the line you touched. Clicking through the app by hand after every change is slow and you'll miss things. Automated tests check it for you in seconds, every time, and they're the reason teams can change big codebases without fear.

The intuition

A test is a small function that runs your code and checks the result with assert:

def test_add():
    assert add(2, 3) == 5

assert condition does nothing when the condition is true and raises AssertionError when it's false. Add a message to say what went wrong: assert got == 5, f"got {got}".

Most tests follow three steps: arrange (set up the input), act (call the code), assert (check the result).

A test runner finds every function whose name starts with test_, runs it, and reports which passed. In real projects that's pytest:

$ pytest
test_shop.py ..F                       [100%]
FAILED test_shop.py::test_extra_spaces

Here we use a ten-line runner that does the same job.

A good test fails when the code is wrong. A test that passes no matter what checks nothing.

Definition

assert

A statement that raises AssertionError when its condition is false. Tests are built from asserts.

Test runner

A tool that finds test functions, runs them and reports passes and failures. pytest is the usual one for Python.

Regression

Something that used to work and broke after a change. Tests exist mainly to catch regressions.

Examples

Silent when true, an error when false.

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

assert add(2, 3) == 5
print("first check passed")

try:
    assert add(2, 2) == 5, "2 + 2 should not be 5"
except AssertionError as e:
    print("failed:", e)
Output
first check passed
failed: 2 + 2 should not be 5
Step by step
  1. L4True, so nothing happens and the program carries on.
  2. L8False: AssertionError with our message.
  3. L10We catch it here only to show the message; a test runner does this for you.failed: 2 + 2 should not be 5

Three tests and a runner

Try it yourself

Each test checks one behaviour; the runner reports them all.

def slugify(title):
    return title.lower().replace(" ", "-")

def test_simple():
    got = slugify("Hello World")
    assert got == "hello-world", f"got {got!r}"

def test_already_lower():
    got = slugify("abc")
    assert got == "abc", f"got {got!r}"

def test_extra_spaces():
    got = slugify("  Hi  there ")
    assert got == "hi-there", f"got {got!r}"

def run(tests):
    passed = 0
    for test in tests:
        try:
            test()
            print("PASS", test.__name__)
            passed += 1
        except AssertionError as e:
            print("FAIL", test.__name__, "-", e)
    print(f"{passed}/{len(tests)} passed")

run([test_simple, test_already_lower, test_extra_spaces])
Output
PASS test_simple
PASS test_already_lower
FAIL test_extra_spaces - got '--hi--there-'
2/3 passed
Step by step
  1. L1The code under test: turn a title into a URL part.
  2. L13An awkward input: spaces at both ends and doubled.
  3. L23A failing test doesn't stop the others.
  4. L27Two pass. The third shows exactly what came back.

Common mistakes

Tests that can't fail

assert slugify("a") passes for any non-empty result. Compare with the exact expected value.

Testing too much in one test

When a test with ten asserts fails, you don't know which behaviour broke. One behaviour per test, with a name that says which.

Only testing the happy path

Bugs hide in the awkward cases: empty input, extra spaces, zero, the biggest value. The next lesson is about finding them.

Quick quiz

1. What does `assert total == 10` do when total is 10?

  1. ANothing; the program carries on
  2. BPrints True
  3. CRaises AssertionError
Show the answer

Answer: A

assert only speaks up when the condition is false.

2. Which test is the most useful?

  1. Aassert price_with_tax(100) == 120
  2. Bassert price_with_tax(100)
  3. Cassert price_with_tax(100) is not None
Show the answer

Answer: A

Only the exact comparison fails when the calculation is wrong.

Practice

3 exercises in three steps: predict what a program prints, change a program to meet a new requirement, then write one from scratch. Your code runs in the browser and is checked the moment you submit.

  1. PredictRead the report

    Type exactly what this prints.

  2. ModifyMake the tests pass

    test_extra_spaces fails. Fix slugify (not the test) so all three tests pass: extra spaces anywhere should disappear, and words are joined by single dashes.

  3. WriteTests that catch bugs

    discount(price, percent) takes a percentage off a price and rounds to cents. Below it are three broken versions. Write test_ functions so that they pass on discount but fail on each broken version.

    passes(version) runs your tests against a version; the grader calls it on the real one, the broken ones, and one more broken version you can't see. The one test you have now catches nothing.

Practise this lesson

Open the interactive lesson to edit and run these examples and solve the exercises with instant feedback. Free to start, no sign-up.

Terms in this lesson

TermChineseDefinition
assertassert 断言Raises AssertionError when its condition is false.
Unit test单元测试A small automated test of one piece of code.
pytestpytestThe most common test runner for Python.