Skip to main content

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 return values and occasional exceptions.

🎯 What you'll learn

  • Author a minimal TestCase with 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

  1. Parallel treesrc/myapp/... plus tests/... (imports target installed packages once you adopt packaging lessons).
  2. Colocatedfeatures/foo.py beside features/test_foo.py for tightly scoped prototypes.

Consistency matters more than zealotry; pick what your editor and CI tooling expect.


💡 Key takeaways

  • unittest is everywhere—contributing upstream often requires reading TestCase subclasses even if pytest ultimately runs them via compatibility shims.

➡️ Next steps

Expand assertion fluency plus discovery ergonomics in unittest Part 2.