Write Unit Tests with unittest
Learn to write unit tests using Python's unittest module. This hands-on lesson covers core concepts, step-by-step walkthroughs, and troubleshooting for reliable code testing.
Focus: write unit tests with unittest module
You've written Python functions, built classes, and created packages—but how do you know your code works correctly after every change? Without automated tests, you're one refactor away from a silent bug. This lesson teaches you to write unit tests with Python's built-in unittest module, transforming vague confidence into repeatable, machine-verified proof.
The problem this lesson solves
Manual testing is slow, error-prone, and impossible to scale. When you change one function, you can't manually re-run every scenario in a 500-line module. Unit tests capture those scenarios as code. They give you an immediate red or green signal so you can refactor fearlessly and catch regressions the moment they appear.
Core concept / mental model
Think of a unit test as a safety net. Each test is a tiny program that: 1. Sets up a known starting state 2. Calls the function or method you want to check 3. Asserts that the result matches your expectation
unittest is Python's standard testing framework. It provides TestCase as the base class, and you write test methods inside that class. Each test method enforces one **assertion**—a claim about your code's behavior.
Pro tip: A good unit test tests one behavior, not one function. A function with three conditional branches may need three tests.
How it works step by step
Step 1: Create a test file
Name your file with a test_ prefix (e.g., test_calculator.py). This is a naming convention that unittest's test runner recognizes.
Step 2: Import unittest and your code
import unittest
from calculator import add, divide # your module
Step 3: Define a test class that inherits from unittest.TestCase
class TestCalculator(unittest.TestCase):
def test_add_positive_numbers(self):
result = add(2, 3)
self.assertEqual(result, 5)
Step 4: Use assertion methods
Common assertions include:
- assertEqual(a, b) — checks a == b
- assertTrue(x) — checks x is True
- assertFalse(x) — checks x is False
- assertRaises(SomeException, callable, arg) — checks that an exception is raised
Step 5: Run the test
From the terminal:
python -m unittest test_calculator.py
You'll see a dot (.) for each passing test, an F for failure, or an E for error.
Hands-on walkthrough
Let's build a full example. Write a module calc.py:
# calc.py
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
def is_even(n):
return n % 2 == 0
Now create test_calc.py:
import unittest
from calc import add, divide, is_even
class TestCalc(unittest.TestCase):
def setUp(self):
"""Runs before every test method."""
self.numbers = [1, 2, 3]
def test_add_integers(self):
self.assertEqual(add(2, 3), 5)
def test_add_floats(self):
self.assertAlmostEqual(add(0.1, 0.2), 0.3, places=5)
def test_divide_positive(self):
self.assertEqual(divide(10, 2), 5.0)
def test_divide_by_zero(self):
with self.assertRaises(ValueError):
divide(5, 0)
def test_is_even_true(self):
self.assertTrue(is_even(4))
def test_is_even_false(self):
self.assertFalse(is_even(3))
if __name__ == '__main__':
unittest.main()
Run it:
$ python -m unittest test_calc.py
......
----------------------------------------------------------------------
Ran 6 tests in 0.001s
OK
Six dots = six passing tests. If you change add to return a + b + 1, the next run would show F..... and explain the failure.
Compare options / when to choose what
| Tool | Use Case | Best For |
|---|---|---|
unittest |
Built-in, no extra install | Team standards, CI, large projects |
pytest |
Third-party, simpler syntax | Rapid TDD, small scripts, less boilerplate |
doctest |
Tests inside docstrings | Quick acceptance checks, documentation examples |
Pro tip: Start with
unittestif you want a zero-dependency solution. Switch topytestif you need powerful fixtures or parameterization later.
Troubleshooting & edge cases
Test method names must start with test_
If you name a method add_test, unittest will silently ignore it. Always prefix with test_.
Floating-point comparison
assertEqual(0.1 + 0.2, 0.3) will fail due to rounding. Use assertAlmostEqual or round:
self.assertAlmostEqual(add(0.1, 0.2), 0.3, places=5)
Testing exceptions with context manager
Use with self.assertRaises(...) to ensure an exception is raised within that block. If the exception isn't raised, the test fails.
setUp and tearDown
Define setUp() to create common test data once per test. It runs before every test method. Similarly, tearDown() runs after each test to clean up resources (e.g., close file handles).
Test discovery
Run all tests in a directory:
python -m unittest discover -s tests -p "test_*.py"
This finds all files matching test_*.py in the tests/ folder.
What you learned & what's next
You now know how to write unit tests with the unittest module: creating TestCase subclasses, using assertion methods, running tests from the command line, and handling common edge cases like floating-point precision and exception testing. This skill lets you verify your code automatically and refactor with confidence.
Next, you'll explore test coverage—measuring how much of your code is actually exercised by your tests. Or, if you're ready to dive deeper into Python's testing ecosystem, look into unittest.mock for replacing dependencies during testing.
Keep testing everything—your future self will thank you.
Practice recap
Now write a test suite for your own small project. Pick a module with at least three functions (one that raises an exception on invalid input, one that returns a boolean, and one that computes a numeric result). Create test_<module>.py with six test methods covering normal cases and edge cases. Run the tests and fix any failures. Then add a setUp() method to share test data across multiple tests.
Common mistakes
- Forgetting to prefix test method names with
test_—unittestsilently skips methods that don't start withtest_. - Using
assertEqualfor floating-point numbers —0.1 + 0.2 != 0.3due to binary representation; useassertAlmostEqualinstead. - Calling
unittest.main()inside the wrongifblock — always guard withif __name__ == '__main__'to prevent running tests on import. - Writing tests that depend on test execution order — each test should be independent. Shared state in class attributes can cause intermittent failures.
Variations
- Use
pytestfor simpler syntax:def test_add(): assert add(2,3) == 5— no class required. - Use
doctestto embed tests directly in docstrings:>>> add(2,3) 5— ideal for simple examples in documentation. - Use
hypothesislibrary for property-based testing: automatically generates many inputs to find edge cases.
Real-world use cases
- A CI pipeline runs
python -m unittest discoveron every pull request to catch regressions before merging. - An API client module uses
unittest.mockto simulate HTTP responses, isolating network calls from core logic tests. - A financial calculation library uses
assertAlmostEqualwithplaces=2to verify currency computations without floating-point drift.
Key takeaways
- Unit tests automate verification of individual functions or methods, catching regressions immediately.
- Use
unittest.TestCaseas a base class and write test methods prefixed withtest_. - Choose the right assertion:
assertEqual,assertTrue,assertRaises, andassertAlmostEqualfor floats. - Run tests with
python -m unittest <file>or usediscoverfor entire test directories. - Always name test files with a
test_prefix to enable automatic discovery. - Independent, isolated tests lead to reliable suites — avoid sharing state across tests.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.