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

TDD and regression tests · Software Testing

When is the best moment to write a test?

Course contents

Why you need it

Writing the test first sounds backwards, but it's one of the most useful habits in programming. It forces you to decide what the code should do before you write it, proves the test can actually fail, and leaves a guard behind that stops the same bug from ever coming back.

The intuition

Test-driven development (TDD) is a short loop:

  1. Red: write a test for the next small behaviour. Run it: it fails (the code doesn't do it yet).
  2. Green: write the simplest code that makes it pass.
  3. Refactor: tidy the code while all tests stay green.

Fixing a bug works the same way. Before touching the code, write a test that reproduces the bug report. Watch it fail: now you know you've understood the bug. Fix the code, watch it pass. Keep the test forever: it's a regression test, and if anyone brings the bug back, it fails.

Read a failing test from the bottom: what was expected, what came back, on which input. That's usually enough to find the cause.

Definition

TDD

Test-driven development: write a failing test, make it pass, then tidy up, in small steps.

Regression test

A test written for a bug that was fixed, kept so the bug can't silently return.

Examples

Reproduce, then fix

Try it yourself

Bug report: word_count(" hello world ") returns 7. The test fails first, then passes after the fix.

def word_count_old(text):
    return len(text.split(" "))

def word_count_new(text):
    return len(text.split())

def test_extra_spaces(word_count):
    got = word_count("  hello   world ")
    assert got == 2, f"expected 2, got {got}"

for version in [word_count_old, word_count_new]:
    try:
        test_extra_spaces(version)
        print(version.__name__, "PASS")
    except AssertionError as e:
        print(version.__name__, "FAIL:", e)
Output
word_count_old FAIL: expected 2, got 7
word_count_new PASS
Step by step
  1. L2split(" ") makes an empty string for every extra space.
  2. L5split() with no argument skips runs of spaces.
  3. L7The regression test, written from the bug report.
  4. L11Red with the old code, green with the new.

Red, green, refactor

Try it yourself

Three tiny TDD rounds for FizzBuzz, each driven by one new test.

def fizzbuzz(n):
    if n % 15 == 0:
        return "FizzBuzz"
    if n % 3 == 0:
        return "Fizz"
    if n % 5 == 0:
        return "Buzz"
    return str(n)

TESTS = [(1, "1"), (3, "Fizz"), (5, "Buzz"), (15, "FizzBuzz"), (30, "FizzBuzz")]
for n, want in TESTS:
    assert fizzbuzz(n) == want, (n, fizzbuzz(n))
print("all", len(TESTS), "tests green")
Output
all 5 tests green
Step by step
  1. L8Round 1: the test for 1 → "1" only needed this line.
  2. L5Round 2: the test for 3 added this branch.
  3. L3Round 3: 15 failed with "Fizz", so this check had to come first.

Common mistakes

Fixing before reproducing

If you never saw the test fail, you don't know whether it tests the bug at all. Red first.

Changing the test to make it pass

When a test fails, ask which is wrong: the code or the test. Only change the expected value if the requirement really says so.

Giant steps

Writing ten tests and then all the code loses the point. One small test, make it pass, repeat.

Quick quiz

1. A bug is reported. What should you do first?

  1. AWrite a test that reproduces it and watch it fail
  2. BChange the code until it seems fixed
  3. CDelete the tests that fail
Show the answer

Answer: A

A failing test proves you understand the bug, then proves the fix works, then guards against it forever.

2. In TDD, what comes right after writing a failing test?

  1. AWrite the simplest code that makes it pass
  2. BRefactor everything
  3. CWrite ten more tests
Show the answer

Answer: A

Red → green → refactor, in small steps.

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. PredictWhich tests fail?

    Type exactly what this prints.

  2. ModifyFix bug #42

    The regression test for bug #42 is already written and fails. Fix parse_price so it passes, and also handle spaces around the text (" $12,000 " → 12000.0). Don't change the test.

  3. WriteMake the tests green

    The tests came first. Write to_roman(n) for 1 to 3999 so they all pass:

    1 → I   4 → IV   9 → IX   14 → XIV   40 → XL   90 → XC
    400 → CD   1994 → MCMXCIV   2026 → MMXXVI   3999 → MMMCMXCIX

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.

Interactive lessons open in order. If you have not done the earlier ones yet, you start at the course map.

Terms in this lesson

TermChineseDefinition
TDD测试驱动开发Red, green, refactor: write the test first.
Regression test回归测试A test kept so a fixed bug can't come back.