Introduction to unittest (Part 2): Assertions, test discovery, and running tests
Part 1 showed TestCase scaffolding. You now need richer assertions, lifecycle hooks, and predictable discovery so suites scale past a handful of files.
📚 Prerequisites
- Basic
unittest.TestCaseexamples.
🎯 What you'll learn
- Choose specific assertion helpers for clearer failure output.
- Control discovery flags (
-k,-f, verbosity). - Use
setUp/tearDownwithout entangling shared global state.
Assertion cookbook
| Method | When to use |
|---|---|
assertEqual(a, b) | Value equality with helpful diffs |
assertAlmostEqual | Floating tolerances |
assertRaises / assertRaisesRegex | Expected errors |
assertIsNone / assertIsNotNone | Optional returns |
assertIn / assertCountEqual | Collections membership |
assertTrue / assertFalse | Last resort—prefer concrete checks |
import unittest
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("b must be nonzero")
return a / b
class TestDivide(unittest.TestCase):
def test_zero_denominator(self):
with self.assertRaises(ValueError):
divide(10, 0)
Discovery tips
python -m unittest discover -s tests -p "test_*.py" -v
-s sets the start directory; -p tweaks the pattern when your organization standardizes naming; -v prints each test method for long CI logs.
Stop on first failure while iterating locally:
python -m unittest discover -f
Setup and teardown hooks
class TestTmp(unittest.TestCase):
def setUp(self):
self.fixture = {"count": 0}
def tearDown(self):
# release resources opened in setUp
self.fixture.clear()
Prefer fresh fixtures per test unless setUpClass’s expensive resource is immutable (read-only databases, compiled models).
💡 Key takeaways
- Tests should read like specifications; verbose assertion methods double as documentation for future maintainers.
➡️ Next steps
Switch to pytest’s function style in Introduction to pytest (Part 1).