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.
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.
Output
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.
Output
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.
Output
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.
Output
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.
Output
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.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.