Part of Python for Beginners

Unittest, assertEqual, setUp & tearDown Explained - Python Unit Testing Tutorial #28

Sandy LaneSandy Lane

Video: Unittest, assertEqual, setUp & tearDown Explained - Python Unit Testing Tutorial #28 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Python Unit Testing with unittest

class TestX(unittest.TestCase): groups tests. Methods starting with test_ are auto-discovered. assertEqual, assertTrue, assertRaises make claims. setUp/tearDown for per-test fixtures. python -m unittest runs the suite.

Tests catch bugs before users do. Python's unittest module is built in — no install needed.

A first test

# math_tools.py
def add(a, b):
  return a + b

def divide(a, b):
  if b == 0:
    raise ValueError("Cannot divide by zero")
  return a / b
# test_math_tools.py
import unittest
from math_tools import add, divide

class TestMathTools(unittest.TestCase):

  def test_add(self):
    self.assertEqual(add(2, 3), 5)
    self.assertEqual(add(-1, 1), 0)

  def test_divide(self):
    self.assertEqual(divide(10, 2), 5.0)

  def test_divide_by_zero(self):
    with self.assertRaises(ValueError):
      divide(10, 0)

if __name__ == "__main__":
  unittest.main()

Run it:

python -m unittest test_math_tools.py

Output:

...
Ran 3 tests in 0.001s
OK

Each . is a passing test. F for failure, E for error.

Test discovery

python -m unittest                  # discover all test_*.py in the cwd
python -m unittest discover         # same
python -m unittest discover tests/  # specific directory
python -m unittest test_math_tools  # specific module
python -m unittest test_math_tools.TestMathTools.test_add   # specific test

By default, unittest finds files matching test*.py and within them, classes inheriting from TestCase and methods starting with test_.

Assertions

Assertion Checks
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, b) a is b (identity)
assertIsNone(x) x is None
assertIn(a, b) a in b
assertIsInstance(a, cls) isinstance(a, cls)
assertAlmostEqual(a, b) floats nearly equal
assertRaises(Error) block raises Error
assertGreater(a, b) a > b
assertCountEqual(a, b) same elements (any order)

These give better failure messages than assert a == b. When a test fails, you see what each was, not just "False is not True."

setUp and tearDown

class TestGradebook(unittest.TestCase):

  def setUp(self):
    self.gb = Gradebook()
    self.gb.add_student("Alice")
    self.gb.add_student("Bob")

  def tearDown(self):
    pass    # cleanup if needed

  def test_add_student(self):
    self.gb.add_student("Charlie")
    self.assertIn("Charlie", self.gb.students)

  def test_add_grade(self):
    self.gb.add_grade("Alice", 95)
    self.assertEqual(self.gb.students["Alice"], [95])

setUp runs before each test method. tearDown runs after. Use them for per-test fixtures: fresh objects, temporary files, mocks.

For class-wide setup that should run once:

@classmethod
def setUpClass(cls):
  cls.expensive_resource = create_expensive_thing()

@classmethod
def tearDownClass(cls):
  cls.expensive_resource.cleanup()

assertRaises

def test_invalid_grade(self):
  with self.assertRaises(ValueError):
    self.gb.add_grade("Alice", 150)

Verify that a block raises a specific exception. The test passes only if the exception type matches.

To inspect the exception:

with self.assertRaises(ValueError) as ctx:
  divide(10, 0)
self.assertIn("zero", str(ctx.exception))

Skipping and expected failures

import sys

@unittest.skip("not yet implemented")
def test_unfinished(self):
  ...

@unittest.skipIf(sys.platform == "win32", "Linux only")
def test_unix_only(self):
  ...

@unittest.expectedFailure
def test_known_bug(self):
  self.assertEqual(1, 2)

Useful for in-progress code, platform-specific tests, and tracking known-broken cases without breaking the suite.

Mocking with unittest.mock

from unittest.mock import patch, MagicMock

@patch("module.requests.get")
def test_fetch(self, mock_get):
  mock_get.return_value.json.return_value = {"data": [1, 2, 3]}
  result = fetch_data()
  self.assertEqual(result, [1, 2, 3])
  mock_get.assert_called_once()

@patch replaces a name with a MagicMock for the duration of the test. Use it to fake external calls (HTTP, DB, time) so tests are fast and deterministic.

MagicMock() returns another MagicMock for any attribute or method call. You configure return values and verify calls.

Verbosity and output

python -m unittest -v               # verbose: shows each test name
python -m unittest -f               # fail fast: stop after first failure
python -m unittest -k pattern       # only tests matching pattern

Verbose output:

test_add (test_math_tools.TestMathTools) ... ok
test_divide (test_math_tools.TestMathTools) ... ok
test_divide_by_zero (test_math_tools.TestMathTools) ... ok

Ran 3 tests in 0.001s
OK

A test for a stateful class

class TestGradebook(unittest.TestCase):

  def setUp(self):
    self.gb = Gradebook()
    self.gb.add_student("Alice")

  def test_add_grade(self):
    self.gb.add_grade("Alice", 95)
    self.assertEqual(self.gb.students["Alice"], [95])

  def test_get_average(self):
    self.gb.add_grade("Alice", 90)
    self.gb.add_grade("Alice", 80)
    self.assertEqual(self.gb.get_average("Alice"), 85.0)

  def test_invalid_grade(self):
    with self.assertRaises(ValueError):
      self.gb.add_grade("Alice", 150)

Each test gets a fresh Gradebook from setUp. They run in any order, never depend on each other.

Test layout conventions

myproject/
  src/
    myproject/
      gradebook.py
  tests/
    __init__.py        # may be empty
    test_gradebook.py

Common patterns:

  • One test_X.py per module under test.
  • Test class names TestX for clarity.
  • Test method names describe the case: test_add_grade_invalid_value.

Pytest is the popular alternative — works with the same test_*.py discovery, cleaner syntax (assert x == y instead of self.assertEqual), better fixtures. For new projects, pytest is the standard choice. But unittest works everywhere without install.

Coverage

pip install coverage
coverage run -m unittest discover
coverage report
coverage html       # opens htmlcov/index.html — line-by-line

Coverage measures which lines of your code are exercised by tests. 100% coverage is a goal, not a guarantee — covered code can still have bugs. But low coverage almost certainly hides them.

What good tests look like

  • Fast. Milliseconds. No network, no DB unless integration test.
  • Independent. Each test stands alone. Order doesn't matter.
  • Repeatable. Same input, same result, every time.
  • Clear. Test name says what's being verified.
  • One thing per test. Easier to diagnose failures.

Common stumbles

Test method without test_ prefix. Discovery skips it.

setUp not running. Did you spell it setup or setUP? Capital S, capital U: setUp.

Mutable class attribute as fixture. class TestX: items = [] shares across tests. Always create fresh in setUp.

Tests that depend on each other. Test A leaves state, Test B depends on it. Order matters → flaky tests. Use setUp to isolate.

Asserting on test side effects. A test that prints "Looks good!" but never asserts isn't a test. Always make a claim.

Mocking too much. Tests pass but don't reflect real behavior. Mock external systems; test your real code.

Skipping if __name__ == "__main__":. Without it, python -m unittest still finds tests, but python test_file.py doesn't run anything.

What's next

Lesson 29: type hints. Static typing in Python — List[int], Optional, Union, mypy.

Recap

unittest.TestCase subclass with test_* methods. Assertions with assertEqual, assertRaises, etc. for clear failure messages. setUp/tearDown for per-test fixtures, setUpClass for once-per-class. python -m unittest discovers and runs. unittest.mock for replacing external dependencies. Pytest is the popular alternative for new projects. Tests should be fast, independent, repeatable, and one-thing-per-test.

Next lesson: type hints.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.