A unit test checks one function
A unit test is a small program that calls one function with a known input and checks the answer. Python ships with a tool for it, called unittest, so you need to install nothing.
A shop changes the way it takes a discount off a basket. A unit test tells the developer in a second whether the change broke the old sums.
By the end of this lesson
Note
I can write a unit test with assertions and run it with python3 -m unittest.
Key words
| Word | What it means | Example |
|---|---|---|
| Assertion | a check that stops the test if it is false | assertEqual(a, b) |
| Test case | a class that holds related tests | |
| Test method | one test, a method whose name starts with test | |
| Arrange, act, assert | the three steps of a test | |
| Test suite | all the tests that are run together |
The cells students fill in are left blank in this preview.
Every test arranges, acts and asserts
Arrange: set up the input and anything the code needs. Act: call the function once. Assert: compare the result with the answer you expect.
Keep one idea in each test. If a test fails, its name should tell you what broke.
Worked example: the three steps for a basket
| Step | What it does | In code |
|---|---|---|
| Arrange | Two items, 250 and 199 pence, and no discount | prices = [250, 199] |
| Act | Call the function once | result = basket_total(prices, 0) |
| Assert | The total should be 449 | assertEqual(result, 449) |
The cells students fill in are left blank in this preview.
Plain assert here, unittest in the lab terminal
This module uses two styles of test, and it is said once. In the lab terminal you write tests with the unittest module and run them with python3 -m unittest. The assignments on this platform use plain assert in functions named test_, because the platform runs those for you.
The ideas are the same in both: arrange, act, assert.
Make a folder for the tests
mkdir -p ~/shoptestShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Go into it
cd ~/shoptestShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the function to test
printf 'def basket_total(prices, discount_percent):\n total = sum(prices)\n return total - total * discount_percent // 100\n' > basket.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write one test
printf 'import unittest\nfrom basket import basket_total\n\n\nclass BasketTest(unittest.TestCase):\n def test_no_discount(self):\n self.assertEqual(basket_total([250, 199], 0), 449)\n' > test_basket.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A unittest file holds test methods
The file you just wrote imports the function, then makes a class that inherits from unittest.TestCase. Each method whose name starts with test is one test. self.assertEqual(a, b) is the assertion: it fails the test when a and b differ.
The runner finds the methods by their names, so a method called check_no_discount would never run.
Run the tests
python3 -m unittest test_basketShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
How to read a passing report
A dot stands for one test that passed. The report ends with the number of tests it ran, and then OK. The time it quotes differs from run to run.
Add -v to see one line for every test, with its name and the word ok.
Run again, one line per test
python3 -m unittest -v test_basketShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A failing test shows expected and actual
The shop's rule is that the discount is rounded to the nearest penny. A 10 percent discount on 449 pence is 44.9 pence, so it should be 45, and the total 404.
Add that test. It should fail, because the code rounds the discount down.
Add a test for a 10 percent discount
printf ' def test_ten_percent(self):\n self.assertEqual(basket_total([250, 199], 10), 404)\n' >> test_basket.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run the tests and watch one fail
python3 -m unittest test_basketShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Read a failure from the bottom up
The report names the test that failed, shows the line, and ends with AssertionError: 405 != 404. The left number is what the code gave. The right one is what the test expected. The last line says FAILED (failures=1).
The test is right and the code has the fault. Change the code to round the discount, then run again.
Fix the fault: round the discount
sed -i 's|total \* discount_percent // 100|round(total * discount_percent / 100)|' basket.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run again: both tests pass
python3 -m unittest test_basketShown 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 copy the answer from the code into the test. Then the test agrees with the code even when both are wrong. Work out the expected answer from the rule, by hand, before you look at what the code gives.