Test-driven development writes the test before the code
Test-driven development, or TDD, is a way of working. You write a failing test first, then just enough code to make it pass, then tidy up. You repeat that in small steps.
A team that works this way always has a test for the line it just wrote. It also writes only the code that something asks for.
By the end of this lesson
Note
I can write a failing test first, make it pass and tidy up.
Key words
| Word | What it means | Example |
|---|---|---|
| Test-driven development | writing the test before the code it checks | TDD |
| Red | the step where a new test fails | |
| Green | the step where all tests pass | |
| Refactor | to tidy the code without changing what it does | |
| Stub | the smallest code that lets a test run and fail |
The cells students fill in are left blank in this preview.
The cycle is red, green, refactor
Red: write one small test for a behaviour that does not exist yet, and watch it fail. Green: write the least code that makes every test pass. Refactor: tidy the code, and run the tests again to see that nothing broke.
Then pick the next small behaviour and go round again.
Why watch a test fail first
A test that has never failed might be unable to fail. Maybe it checks nothing. By running it before the code exists, you see it go red for the right reason.
You also meet the design from the outside first: how a caller will use the function, before you decide how it works inside.
Make a folder for this lesson
mkdir -p ~/tddtestShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Go into it
cd ~/tddtestShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Red: write the first test, before any code
printf 'import unittest\nfrom people import initials\n\n\nclass InitialsTest(unittest.TestCase):\n def test_two_names(self):\n self.assertEqual(initials("Ada Lovelace"), "AL")\n' > test_people.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run it: nothing exists yet
python3 -m unittest test_peopleShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
The first red is an error
The Run ended with ModuleNotFoundError: No module named 'people', and FAILED (errors=1). There is no people.py yet, so the test cannot even load.
Write a stub: a function that exists and gives a wrong answer. Now the test runs, and fails because the answer is wrong.
Write a stub that returns nothing useful
printf 'def initials(name):\n return ""\n' > people.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run it: red for the right reason
python3 -m unittest test_peopleShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Green: write the least code that passes
printf 'def initials(name):\n parts = name.split()\n return parts[0][0] + parts[-1][0]\n' > people.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run it: green
python3 -m unittest test_peopleShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
The next test finds the gap
The code takes the first and last words. A person with three names, such as Mary Jane Watson, would give the wrong answer. Do not guess. Write the test, and let it fail.
Red again: add a test for three names
printf ' def test_three_names(self):\n self.assertEqual(initials("Mary Jane Watson"), "MJW")\n' >> test_people.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run it: one test fails
python3 -m unittest test_peopleShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Green and tidy: a loop over every name
printf 'def initials(name):\n letters = ""\n for part in name.split():\n letters = letters + part[0]\n return letters\n' > people.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run it: both tests pass
python3 -m unittest test_peopleShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A common mistake
Warning
Many people write the whole function first and the tests afterwards, or write many tests at once. You then do not know which step made a test pass. Add one test, see it fail, write only the code for it, and see it pass. The steps stay small.