Fundamentals 8 min read

Master Python unittest: From Basics to Advanced Techniques

This guide walks through Python's unittest module, covering fundamental concepts, test suites, loaders, custom result handling, skipping tests, retry decorators, execution order, tagging, class-level setup/teardown, and exception‑message assertions, each illustrated with clear code examples.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Master Python unittest: From Basics to Advanced Techniques

1. unittest Basics

The unittest module provides a structured way to write test cases by inheriting from unittest.TestCase and defining methods that start with test_.

2. TestSuite

unittest.TestSuite

acts as a container for multiple test cases, allowing batch execution.

import unittest

class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

class TestNumberMethods(unittest.TestCase):
    def test_add(self):
        self.assertEqual(1 + 2, 3)

def suite():
    suite = unittest.TestSuite()
    suite.addTest(TestStringMethods('test_upper'))
    suite.addTest(TestNumberMethods('test_add'))
    return suite

if __name__ == '__main__':
    runner = unittest.TextTestRunner()
    runner.run(suite())

3. TestLoader

unittest.TestLoader

can automatically discover and load test cases.

import unittest

class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

class TestNumberMethods(unittest.TestCase):
    def test_add(self):
        self.assertEqual(1 + 2, 3)

if __name__ == '__main__':
    loader = unittest.TestLoader()
    tests = loader.discover(start_dir='.', pattern='test*.py')
    runner = unittest.TextTestRunner()
    runner.run(tests)

4. Custom TestResult

By subclassing unittest.TextTestResult, you can customize how test results are displayed.

import unittest

class CustomTestResult(unittest.TextTestResult):
    def addSuccess(self, test):
        super().addSuccess(test)
        print(f"Test succeeded: {test}")

class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

if __name__ == '__main__':
    loader = unittest.TestLoader()
    tests = loader.loadTestsFromTestCase(TestStringMethods)
    runner = unittest.TextTestRunner(resultclass=CustomTestResult)
    runner.run(tests)

5. Skipping Tests

Use @unittest.skip or @unittest.skipIf decorators to omit tests conditionally.

import unittest

class TestStringMethods(unittest.TestCase):
    @unittest.skip("temporarily skip this test")
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])

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

# Conditional skip example
class TestStringMethods(unittest.TestCase):
    @unittest.skipIf(True, "skip when condition is true")
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

6. Retry Decorator

Although unittest lacks built‑in retry, a custom decorator can re‑run flaky tests.

import unittest, time

def retry(max_attempts=3, delay=1):
    def decorator(func):
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    if attempts >= max_attempts:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator

class TestStringMethods(unittest.TestCase):
    @retry(max_attempts=3, delay=1)
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

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

7. Controlling Test Order

By default, tests run in definition order; you can change it by customizing the loader.

import unittest

class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')
    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
    def test_isupper(self):
        self.assertTrue('FOO'.isupper())

if __name__ == '__main__':
    loader = unittest.TestLoader()
    loader.sortTestMethodsUsing = lambda _, x, y: -1 if x > y else 1
    tests = loader.loadTestsFromTestCase(TestStringMethods)
    runner = unittest.TextTestRunner()
    runner.run(tests)

8. Adding Tags via Custom Loader

unittest doesn't support tags directly, but you can filter tests in a custom loader.

import unittest

class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')
    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])

def load_tests(loader, tests, pattern):
    suite = unittest.TestSuite()
    for test_case in tests:
        if hasattr(test_case, '_testMethodName'):
            if 'slow' in getattr(test_case, '_testMethodName'):
                continue
        suite.addTest(test_case)
    return suite

if __name__ == '__main__':
    loader = unittest.TestLoader()
    tests = loader.loadTestsFromTestCase(TestStringMethods)
    tests = load_tests(loader, tests, pattern='test*')
    runner = unittest.TextTestRunner()
    runner.run(tests)

9. setUpClass and tearDownClass

Class‑level setup/teardown run once before/after all tests in the class.

import unittest

class TestStringMethods(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print("setting up test environment")
    @classmethod
    def tearDownClass(cls):
        print("cleaning up test environment")
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')
    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])

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

10. assertRaisesRegex

Use assertRaisesRegex to verify that an exception contains a specific message.

import unittest

class TestStringMethods(unittest.TestCase):
    def test_split(self):
        s = 'hello world'
        with self.assertRaisesRegex(ValueError, "empty separator"):
            s.split('')

if __name__ == '__main__':
    unittest.main()
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

testingRetryunittesttest suiteskipassertRaisesRegexcustom resulttest loader
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.