unittest: test cases and a test run
unittest is the testing framework in Python's standard library. Tests are methods of a TestCase class, its assert methods explain every failure, and a run tells failures from errors.
- Write test methods in a
unittest.TestCaseand check results withassertEqual,assertAlmostEqualandassertIn - Test that code raises the right exception with
with self.assertRaises(...) - Run a TestCase and tell a failure (a wrong answer) from an error (a crash)
unittest comes with Python, so it works everywhere Python does. Its tests live in a TestCase: a class (you'll write your own classes in the Classes & Objects module) whose methods are the tests. Each test method's name must start with test, and it takes one parameter, self, which is the running test. Through self you call the checking methods:
self.assertEqual(actual, expected): on failure it shows both values, so you don't need to write a message;self.assertAlmostEqual(a, b): equal to 7 decimal places, for floats;self.assertIn(item, collection),self.assertTrue(x),self.assertIsNone(x);with self.assertRaises(ValueError):followed by an indented call: the test passes only if that call raises ValueError.
On a computer you run a test file with python -m unittest -v in a terminal. On this page, the small run function at the bottom of each example does the same with unittest's own loader and result object, and prints a summary.
Run each example to see what it prints. Running examples doesn't count toward progress.
import unittest def parse_price(text): price = float(text.strip().replace("£", "")) if price < 0: raise ValueError(f"negative price: {text!r}") return price class TestParsePrice(unittest.TestCase): def test_pound_sign(self): self.assertEqual(parse_price("£4.99"), 4.99) def test_spaces_around(self): self.assertEqual(parse_price(" 12 "), 12.0) def test_negative_is_refused(self): with self.assertRaises(ValueError): parse_price("-3.50") def test_not_a_number(self): with self.assertRaises(ValueError): parse_price("free") def run(case): result = unittest.TestResult() unittest.defaultTestLoader.loadTestsFromTestCase(case).run(result) print("ran", result.testsRun, "| failures", len(result.failures), "| errors", len(result.errors)) for test, details in result.failures + result.errors: print(" " + test.id().split(".", 1)[1], "->", details.strip().splitlines()[-1]) run(TestParsePrice)
A test can go wrong in two ways, and unittest reports them separately. A failure means an assert method found a wrong answer. An error means an exception escaped from the test before it could compare: a bug in the code under test, or in the test itself.
import unittest def order_total(lines): total = 0 for line in lines: total += line["unit_price"] return total class TestOrderTotal(unittest.TestCase): def test_quantities_multiply(self): lines = [{"unit_price": 2.5, "quantity": 4}, {"unit_price": 10.0, "quantity": 1}] self.assertAlmostEqual(order_total(lines), 20.0) def test_empty_order(self): self.assertEqual(order_total([]), 0) def test_price_sent_as_text(self): lines = [{"unit_price": "2.50", "quantity": 1}] self.assertEqual(order_total(lines), 2.5) def run(case): result = unittest.TestResult() unittest.defaultTestLoader.loadTestsFromTestCase(case).run(result) print("ran", result.testsRun, "| failures", len(result.failures), "| errors", len(result.errors)) for test, details in result.failures + result.errors: print(" " + test.id().split(".", 1)[1], "->", details.strip().splitlines()[-1]) run(TestOrderTotal)
When to use it: whenever you want tests that run anywhere without installing anything, or when a project already uses unittest. (pytest, which many projects install, runs unittest TestCases too, and also plain test_ functions with assert.)
Mistakes to avoid:
- A method whose name doesn't start with
testis silently skipped: it never runs, so it never fails. self.assertTrue(total == 20)reports onlyFalse is not true;assertEqual(total, 20)reports both values.assertRaiseswithoutwith:self.assertRaises(ValueError, parse_price("x"))callsparse_price*before* assertRaises can catch anything, so the test errors. Use thewithform.assertEqualon floats from arithmetic: useassertAlmostEqual.
- A
unittest.TestCaseholds test methods namedtest…, each takingself. assertEqualshows both values on failure;assertAlmostEqualfor floats;with self.assertRaises(E):for expected errors.- A failure is a wrong answer; an error is a crash. Methods not named
test…never run. - Next: one test that checks a whole table of inputs, and reports every case that fails.