Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

Testing Python Code with unittest

Write focused unit tests with unittest, choose clear assertions, check expected exceptions, isolate fixtures, and discover tests from the command line.

Levels: Intermediate ~28 min read

Intermediate

A test is an executable check of behavior you expect your program to keep. Python’s built-in unittest framework runs tests, records failures, and gives useful feedback when a change breaks an expectation. This lesson starts with small functions whose results are predictable; it does not require a third-party test package.

Start with a TestCase

Create a class that inherits from unittest.TestCase. Each method whose name begins with test_ is an individual test. Arrange the inputs, call the behavior, and compare the result with the expected value. Tests should describe observable behavior rather than repeat the function’s implementation.

Interactive Runnable example
Edit

Ready to run.

Run this file with python test_keys.py if you save it under that name. The runner reports how many tests ran and whether they passed. The if __name__ == "__main__": guard means the tests run when the file is executed directly but not just because another module imports it.

The runnable lesson uses argv=["example"] and exit=False so the example runs safely inside the interactive lesson runner. In a normal standalone test file, the usual ending is simply unittest.main().

Quick check

Which method name will unittest discover as a test?

import unittest

class TestWords(unittest.TestCase):
    :
        self.assertEqual("FOX".lower(), "fox")

if __name__ == "__main__":
    unittest.main(argv=["quick-check"], exit=False)

Choose assertions that explain a failure

Prefer the assertion that states the expectation clearly. assertEqual(actual, expected) compares values; assertTrue() and assertFalse() check conditions; assertIn() checks membership; and assertAlmostEqual() is useful for rounded numeric results. A focused assertion gives the test runner a clearer failure report than printing a result and visually inspecting it.

Interactive Runnable example
Edit

Ready to run.

Quick check

Which assertion is designed to compare two values and report a useful difference?

import unittest

class TestResult(unittest.TestCase):
    def test_total(self):
        

if __name__ == "__main__":
    unittest.main(argv=["quick-check"], exit=False)

Test errors with assertRaises

Invalid input is part of a function’s contract too. Use with self.assertRaises(ExpectedError): around the operation that should raise the exception. The context manager fails the test if that exception does not occur, and it also fails if a different exception type escapes. Keep the operation being tested inside the context, not unrelated setup code.

Interactive Runnable example
Edit

Ready to run.

Quick check

Where should the operation expected to raise an error appear?

import unittest

def read_count(text):
    return int(text)

class TestCount(unittest.TestCase):
    def test_bad_text(self):
        
            read_count("many")

if __name__ == "__main__":
    unittest.main(argv=["quick-check"], exit=False)

Keep tests independent with fixtures

A fixture is the state or resource a test needs. setUp() runs before each test method and is a good place to create fresh state. tearDown() runs after a successful setup and can release that state. Do not let one test depend on another test’s order or on values left behind by an earlier test. For resources with dedicated context managers, use those managers where appropriate.

Interactive Runnable example
Edit

Ready to run.

Quick check

Which method should initialize fresh fixture state before each test method?

import unittest

class TestFreshState(unittest.TestCase):
    def 
        self.items = []

    def test_starts_empty(self):
        self.assertEqual(self.items, [])

if __name__ == "__main__":
    unittest.main(argv=["quick-check"], exit=False)

Use subTest for related input cases

When one behavior should hold for several small input variations, with self.subTest(...) lets the runner identify each failing case while keeping the shared assertion in one test method. Use it for a compact table of closely related cases; keep unrelated behaviors in separate test methods so the suite remains easy to understand.

Interactive Runnable example
Edit

Ready to run.

Quick check

What does `subTest` help you do?

import unittest

class TestDigits(unittest.TestCase):
    def test_digits(self):
        for value in ["2", "9"]:
            with self.subTest():
                self.assertTrue(value.isdigit())

if __name__ == "__main__":
    unittest.main(argv=["quick-check"], exit=False)

Run and discover tests

From the project’s top-level directory, run python -m unittest to start standard discovery, or use python -m unittest discover -v for a verbose discovery run. By default, discovery looks for importable test modules matching test*.py. You can run one module with python -m unittest -v test_prices or target a particular test method by its fully qualified name. Keep imports safe: merely importing a test module should not perform destructive work or start external services.

Design production functions so inputs and outputs can be tested without launching the whole application. Keep slow network access, real credentials, and destructive operations out of ordinary unit tests; use controlled substitutes when a later, dedicated mocking lesson calls for them. A test suite complements manual checks and integration tests—it does not prove every possible behavior.

Quick check

What does `python -m unittest discover -v` do?

$ 

Key takeaways

  • Use unittest.TestCase and test_-prefixed methods to make test cases discoverable.
  • Assertions state expected behavior directly; assertRaises checks the errors a function promises to reject invalid inputs with.
  • Use setUp() for fresh per-test state and clean up resources even when an assertion fails.
  • Use subTest for related input variations and separate test methods for distinct behavior.
  • Run tests frequently with python -m unittest or python -m unittest discover -v; keep test modules safe to import.

Keep practising

Next, complete the unittest code-completion challenges and check your understanding with the quiz. For the complete API and current command-line options, consult the official Python 3.15 unittest reference, especially its sections on test discovery and organizing test code.

Was this page helpful?

Mark this learning resource complete to track your learning progress.