Introduction to unittest (Part 1): Writing basic test cases
With the testing mindset from article 145, you can lean on Python’s bundled unittest module: test cases are classes extending unittest.TestCase, methods are prefixed with test_, and runners discover files named test*.py.
📚 Prerequisites
- Functions with
returnvalues and occasional exceptions.
🎯 What you'll learn
- Author a minimal
TestCasewith meaningful names. - Run tests locally via
python -m unittest. - Decide between parallel vs. mirrored
tests/trees.
Minimal example
# math_utils.py
def add(a: int, b: int) -> int:
return a + b
# test_math_utils.py
import unittest
from math_utils import add
class TestAdd(unittest.TestCase):
def test_add_positive_numbers(self):
self.assertEqual(add(2, 3), 5)
def test_add_with_negative(self):
self.assertEqual(add(-5, 5), 0)
if __name__ == "__main__":
unittest.main()
Run targeting the module:
python -m unittest test_math_utils.py
Or discover every file starting with test:
python -m unittest discover
Test layout conventions
- Parallel tree —
src/myapp/...plustests/...(imports target installed packages once you adopt packaging lessons). - Colocated —
features/foo.pybesidefeatures/test_foo.pyfor tightly scoped prototypes.
Consistency matters more than zealotry; pick what your editor and CI tooling expect.
💡 Key takeaways
unittestis everywhere—contributing upstream often requires readingTestCasesubclasses even if pytest ultimately runs them via compatibility shims.
➡️ Next steps
Expand assertion fluency plus discovery ergonomics in unittest Part 2.