Published on

Python unittest subTest

subTest() is a context manager on unittest.TestCase that lets you run several related checks inside a single test method while still getting a separate, independently reported failure for each one.

The problem without subTest

Without subTest, a loop that checks multiple values stops at the first failure, hiding any other failing cases in the same run.

import unittest

class TestEven(unittest.TestCase):
    def test_even_numbers(self):
        for i in range(0, 6, 2):
            self.assertEqual(i % 2, 1)  # intentionally wrong, fails on i=0

Running this reports only one failure (i = 0) and the test stops; you never find out whether i = 2 or i = 4 would have also failed.

Using subTest

Wrap the per-iteration assertion in self.subTest(...). Each iteration is now tracked independently, so one failure doesn't prevent the others from running and being reported.

import unittest

class TestEven(unittest.TestCase):
    def test_even_numbers(self):
        for i in range(0, 6, 2):
            with self.subTest(i=i):
                self.assertEqual(i % 2, 1)  # fails every time, on purpose

Output now reports a separate FAIL for i=0, i=2, and i=4, each labeled with the keyword arguments passed to subTest:

FAIL: test_even_numbers (__main__.TestEven) (i=0)
FAIL: test_even_numbers (__main__.TestEven) (i=2)
FAIL: test_even_numbers (__main__.TestEven) (i=4)

Why this matters

  • Better diagnostics: you see every failing case in one run instead of fixing one, re-running, and finding the next.
  • No extra test methods: you avoid writing test_case_1, test_case_2, etc. for what is really one parameterized check.
  • Works with any identifying info: pass whatever keyword arguments help identify the failing iteration, e.g. self.subTest(value=v, expected=e).

Caveats

  • A subTest block still counts toward the enclosing test's pass/fail status — if any sub-test fails, the whole test method is reported as failed.
  • Errors (exceptions other than AssertionError) inside a subTest block still propagate normally and can abort the rest of the loop, depending on the exception.

Reference