Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

How to write a unit test

After this lesson: I can write a unit test with assertions and run it with python3 -m unittest.

55 min lesson10 commandsEveryday Linux commands lab: the thirty commands engineers type from memory

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

WordWhat it meansExample
Assertiona check that stops the test if it is falseassertEqual(a, b)
Test casea class that holds related tests
Test methodone test, a method whose name starts with test
Arrange, act, assertthe three steps of a test
Test suiteall 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

StepWhat it doesIn code
ArrangeTwo items, 250 and 199 pence, and no discountprices = [250, 199]
ActCall the function onceresult = basket_total(prices, 0)
AssertThe total should be 449assertEqual(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

Command
mkdir -p ~/shoptest

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Go into it

Command
cd ~/shoptest

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the function to test

Command
printf 'def basket_total(prices, discount_percent):\n    total = sum(prices)\n    return total - total * discount_percent // 100\n' > basket.py

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write one test

Command
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.py

Shown 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

Command
python3 -m unittest test_basket

Shown 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

Command
python3 -m unittest -v test_basket

Shown 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

Command
printf '    def test_ten_percent(self):\n        self.assertEqual(basket_total([250, 199], 10), 404)\n' >> test_basket.py

Shown 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

Command
python3 -m unittest test_basket

Shown 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

Command
sed -i 's|total \* discount_percent // 100|round(total * discount_percent / 100)|' basket.py

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run again: both tests pass

Command
python3 -m unittest test_basket

Shown 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.

About this lab and its source: Everyday Linux commands lab: the thirty commands engineers type from memory

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.