Writing Unit Tests
Test your functions with Python’s built-in unittest — write tests once, run them every time, and catch bugs the moment they appear.
"Untested code is broken code you haven't found yet. A test suite is a safety net — it catches bugs the moment they're introduced, not six months later in production."
— ShuraiWhy Write Tests at All?
Bugs found by users
Refactoring feels risky
Manual checking after every edit
Refactor confidently
Tests document behaviour
Run 100 checks in 0.1 seconds
The Code to Test
Let’s build a small math_utils.py module and write tests for it:
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
def is_even(n):
return n % 2 == 0
Writing Tests with unittest
A test file follows three rules: import unittest, create a class that inherits from unittest.TestCase, write methods starting with test_:
import unittest
from math_utils import add, subtract, divide, is_even
class TestMathUtils(unittest.TestCase):
def test_add_two_positives(self):
self.assertEqual(add(2, 3), 5) # expected 5, got ?
def test_add_negative_numbers(self):
self.assertEqual(add(-3, -2), -5)
def test_add_zero(self):
self.assertEqual(add(7, 0), 7)
def test_divide_normal(self):
self.assertEqual(divide(10, 2), 5.0)
def test_divide_by_zero_raises(self):
# assertRaises checks that the exception IS raised
with self.assertRaises(ValueError):
divide(5, 0)
def test_is_even_true(self):
self.assertTrue(is_even(4))
self.assertTrue(is_even(0))
def test_is_even_false(self):
self.assertFalse(is_even(7))
if __name__ == "__main__":
unittest.main()
python -m unittest test_math_utils.py -v
# test_add_negative_numbers ... ok
# test_add_two_positives ....... ok
# test_add_zero ................ ok
# test_divide_by_zero_raises ... ok
# test_divide_normal ........... ok
# test_is_even_false ........... ok
# test_is_even_true ............ ok
# -----------------------------------------------
# Ran 7 tests in 0.002s
# OK
The Assert Methods — Cheat Sheet
| Method | Passes when… | Use for |
|---|---|---|
| assertEqual(a, b) | a == b | Most return values |
| assertNotEqual(a, b) | a != b | Result should have changed |
| assertTrue(x) | x is truthy | Boolean checks |
| assertFalse(x) | x is falsy | Boolean checks |
| assertIsNone(x) | x is None | Optional return values |
| assertIn(a, b) | a in b | Lists, strings, dicts |
| assertRaises(Exc) | code raises Exc | Error handling paths |
test_divide_by_zero_raises is better than test_divide2. When a test fails, its name should immediately tell you what scenario broke — saving you from opening the file just to understand what went wrong.
"Write tests for the happy path (normal inputs), the edge cases (zero, empty string, None), and the error paths (what should raise an exception). Cover those three and you'll catch 90% of bugs."
— Shurai🧠 Quiz — Q1
What are the three rules for writing a unittest test file?
🧠 Quiz — Q2
How do you test that a function raises a specific exception?
🧠 Quiz — Q3
A test method is named test_divide_by_zero_raises. Why is this a good name?
🧠 Quiz — Q4
What does python -m unittest test_math.py -v do differently from without -v?