Testing with unittest
Reviewed & published by Brayan K
unittest is Python's built-in testing framework for writing small checks that confirm your code produces the right results and keeps producing them as it changes.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Tests are how professionals ship code with confidence. Write them once and they guard your project forever, catching the bug the instant you introduce it.
What You'll Learn in This Lesson
- Writing a TestCase with test_ methods
- Asserting with assertEqual and assertTrue
- Checking for exceptions with assertRaises
- Sharing setup with setUp and tearDown
- Running tests in-process and reading the result
- How unittest compares to pytest
1 Your First TestCase
Subclass unittest.TestCase and add methods whose names start with test_. Inside each, call your code and assert the result. The framework discovers and runs every test_ method for you:
import unittest
def add(a, b):
return a + b
class TestAdd(unittest.TestCase):
def test_positive(self):
self.assertEqual(add(2, 3), 5) # passes when equal
def test_negative(self):
self.assertEqual(add(-1, -1), -2)
def test_is_int(self):
self.assertTrue(isinstance(add(1, 1), int)) # passes when truthy
if __name__ == "__main__":
unittest.main() # the normal way to run from a fileNormally you run a test file from the terminal with python -m unittest or by calling unittest.main(). To see results here in the browser editor, we run the suite in-process and inspect the result object — the code editor below does exactly that:
import unittest, io
def add(a, b):
return a + b
class TestAdd(unittest.TestCase):
def test_positive(self):
self.assertEqual(add(2, 3), 5)
def test_zero(self):
self.assertTrue(add(0, 0) == 0)
def test_is_int(self):
self.assertTrue(isinstance(add(1, 1), int))
# Run in-process and read the result (clean, deterministic output)
suite = unittest.TestLoader().loadTestsFromTestCase(TestAdd)
result = unittest.TextTestRunner(stream=io.StringIO(), verbosity=0).run(suite)
print("Ran", result.testsRun, "tests") # Ran 3 tests
print("Passed:", result.wasSuccessful()) # Passed: True
print("Failures:", len(result.failures)) # Failures: 0
# ✅ Expected output:
# Ran 3 tests
# Passed: True
# Failures: 02 assertRaises and Catching a Failing Test
Good code fails loudly on bad input, and good tests check that it does. Use assertRaises as a context manager to confirm a specific exception is raised:
import unittest
def divide(a, b):
if b == 0:
raise ValueError("cannot divide by zero")
return a / b
class TestDivide(unittest.TestCase):
def test_basic(self):
self.assertEqual(divide(10, 2), 5)
def test_zero_raises(self):
with self.assertRaises(ValueError): # the block must raise this
divide(1, 0)When an assertion is wrong, the test fails — and the result object tells you so. Here is the same machinery detecting a deliberately broken test:
class TestBroken(unittest.TestCase):
def test_wrong(self):
self.assertEqual(2 + 2, 5) # this WILL fail
# result.wasSuccessful() -> False, and result.failures has one entryA failure is an assertion that did not hold; an error is an unexpected exception in the test itself. The result object reports both separately via result.failures and result.errors.
import unittest, io
def divide(a, b):
if b == 0:
raise ValueError("cannot divide by zero")
return a / b
class TestDivide(unittest.TestCase):
def test_basic(self):
self.assertEqual(divide(10, 2), 5)
def test_zero_raises(self):
with self.assertRaises(ValueError):
divide(1, 0)
def run(case):
suite = unittest.TestLoader().loadTestsFromTestCase(case)
return unittest.TextTestRunner(stream=io.StringIO(), verbosity=0).run(suite)
good = run(TestDivide)
print("Good suite passed:", good.wasSuccessful()) # True
# A deliberately broken test, detected by the framework
class TestBroken(unittest.TestCase):
def test_wrong(self):
self.assertEqual(2 + 2, 5)
bad = run(TestBroken)
print("Broken suite passed:", bad.wasSuccessful()) # False
print("Broken failures:", len(bad.failures)) # 1
# ✅ Expected output:
# Good suite passed: True
# Broken suite passed: False
# Broken failures: 13 Shared Fixtures with setUp and tearDown
When several tests need the same starting object, build it once in setUp — it runs before every test, giving each a fresh, identical fixture so they cannot interfere with one another:
import unittest
class Stack:
def __init__(self):
self.items = []
def push(self, x):
self.items.append(x)
def pop(self):
if not self.items:
raise IndexError("pop from empty stack")
return self.items.pop()
def __len__(self):
return len(self.items)
class TestStack(unittest.TestCase):
def setUp(self):
# Fresh stack for EVERY test method
self.s = Stack()
self.s.push(1)
self.s.push(2)
def tearDown(self):
# Runs after each test (cleanup goes here)
self.s = None
def test_len(self):
self.assertEqual(len(self.s), 2)
def test_pop_order(self):
self.assertEqual(self.s.pop(), 2) # last in, first out
self.assertEqual(len(self.s), 1)
def test_empty_pop_raises(self):
with self.assertRaises(IndexError):
Stack().pop()import unittest, io
class Stack:
def __init__(self):
self.items = []
def push(self, x):
self.items.append(x)
def pop(self):
if not self.items:
raise IndexError("pop from empty stack")
return self.items.pop()
def __len__(self):
return len(self.items)
class TestStack(unittest.TestCase):
def setUp(self):
self.s = Stack() # fresh for every test
self.s.push(1)
self.s.push(2)
def test_len(self):
self.assertEqual(len(self.s), 2)
def test_pop_order(self):
self.assertEqual(self.s.pop(), 2)
self.assertEqual(len(self.s), 1)
def test_empty_raises(self):
with self.assertRaises(IndexError):
Stack().pop()
suite = unittest.TestLoader().loadTestsFromTestCase(TestStack)
result = unittest.TextTestRunner(stream=io.StringIO(), verbosity=0).run(suite)
print("Ran", result.testsRun, "tests") # Ran 3 tests
print("All passed:", result.wasSuccessful()) # All passed: True
# ✅ Expected output:
# Ran 3 tests
# All passed: True⚖️ unittest vs pytest at a Glance
| Aspect | unittest (built-in) | pytest (third-party) |
|---|---|---|
| Style | Classes + methods | Plain functions |
| Assertions | self.assertEqual(a, b) | assert a == b |
| Fixtures | setUp / tearDown | @pytest.fixture |
| Install | None (in the stdlib) | pip install pytest |
| Output | Minimal | Rich, detailed diffs |
The concepts transfer perfectly: master fixtures, assertions, and isolation here, and pytest will feel like the same ideas with less boilerplate.
🎯 Your Turn: Fill in the Blanks
Complete the test so it checks a value and a raised exception. Replace each ___ and match the expected output.
# 🎯 YOUR TURN: complete the test class
import unittest, io
def to_upper(s):
if not isinstance(s, str):
raise TypeError("expected a string")
return s.upper()
class TestToUpper(unittest.TestCase):
# 1. Method names must start with this prefix:
def ___basic(self):
self.assertEqual(to_upper("hi"), "HI")
def test_bad_type(self):
# 2. Assert that calling with an int raises TypeError:
with self.___(TypeError):
to_upper(123)
suite = unittest.TestLoader().loadTestsFromTestCase(TestToUpper)
result = unittest.TextTestRunner(stream=io.StringIO(), verbosity=0).run(suite)
print("Ran", result.testsRun, "tests") # Ran 2 tests
print("Passed:", result.wasSuccessful()) # Passed: True
# Expected output:
# Ran 2 tests
# Passed: TrueFirst blank: test_ — so the method is def test_basic(self):. Only methods starting with test_ run.
Second blank: assertRaises — so the line is with self.assertRaises(TypeError):.
Common Errors to Avoid
❌ Naming a method without the test_ prefix
def check_add(self): # never runs — unittest ignores it
self.assertEqual(add(2, 2), 4)✅ Start every test method with test_: def test_add(self):.
❌ Calling the function inside assertRaises
self.assertRaises(ValueError, divide(1, 0)) # divides BEFORE the check -> the error escapes✅ Use the context-manager form so the call happens inside: with self.assertRaises(ValueError): divide(1, 0).
❌ Reversing assertEqual arguments and misreading the message
self.assertEqual(expected, actual) # works, but order matters for the diff message✅ Convention is assertEqual(actual, expected) — the value from your code first, the expected value second.
🎯 Mini Challenge: Test a Validator
Write tests for a small age validator: it returns True for a valid adult age and raises ValueError on a negative age. Cover the happy path, a boundary, and the error case.
import unittest, io
def is_adult(age):
if age < 0:
raise ValueError("age cannot be negative")
return age >= 18
class TestIsAdult(unittest.TestCase):
def test_adult(self):
self.assertTrue(is_adult(21))
def test_minor(self):
self.assertFalse(is_adult(15))
def test_boundary(self):
self.assertTrue(is_adult(18)) # exactly 18 counts
def test_negative_raises(self):
with self.assertRaises(ValueError):
is_adult(-5)
suite = unittest.TestLoader().loadTestsFromTestCase(TestIsAdult)
result = unittest.TextTestRunner(stream=io.StringIO(), verbosity=0).run(suite)
print("Ran", result.testsRun, "tests") # Ran 4 tests
print("Passed:", result.wasSuccessful()) # Passed: True
print("Failures:", len(result.failures)) # Failures: 0
# ✅ Expected output:
# Ran 4 tests
# Passed: True
# Failures: 0📚 Keep Learning
Go deeper with the official Python documentation:
- unittest — Unit testing framework
- The full list of assert methods
- Organizing test code (setUp, tearDown, fixtures)
Frequently Asked Questions
Lesson complete — you can prove your code works!
You can write a TestCase with test_ methods, assert with assertEqual, assertTrue, and assertRaises, share fixtures with setUp and tearDown, run a suite in-process to read its result, and you know how unittest maps onto pytest.
Practice quiz
What must a unittest test method's name start with to be discovered and run?
- check_
- assert_
- test_
- unittest_
Answer: test_. Only methods whose names start with test_ are run. A method like check_add silently never runs, so the suite may be testing nothing.
What do you subclass to create a test case?
- unittest.TestCase
- unittest.Test
- unittest.Suite
- unittest.Runner
Answer: unittest.TestCase. You subclass unittest.TestCase and add test_ methods; the framework discovers and runs each one.
Which assertion passes only when two values are equal?
- assertTrue(a, b)
- assertSame(a, b)
- assertIs(a, b)
- assertEqual(a, b)
Answer: assertEqual(a, b). assertEqual(a, b) passes when a == b and is the assertion you'll use most often.
How do you assert that code raises a specific exception?
- self.assertRaises(ValueError, divide(1, 0))
- with self.assertRaises(ValueError): divide(1, 0)
- self.assertError(ValueError)
- with self.expectRaises(ValueError): divide(1, 0)
Answer: with self.assertRaises(ValueError): divide(1, 0). Use the context-manager form so the call happens inside the block. Writing assertRaises(ValueError, divide(1, 0)) calls divide first, letting the error escape.
When does setUp run?
- Before every single test method
- Once before the whole test class
- Only before the first test
- After each test method
Answer: Before every single test method. setUp runs before each test method, giving every test a fresh, identical fixture so tests cannot interfere with one another.
When does tearDown run?
- Before each test method
- Once at the end of all tests
- After each test method
- Only if a test fails
Answer: After each test method. tearDown runs after each test method — the place to clean up the fixtures that setUp created.
In unittest, what is the difference between a 'failure' and an 'error'?
- They are the same thing
- A failure is an assertion that did not hold; an error is an unexpected exception in the test
- A failure is in setUp; an error is in tearDown
- A failure stops the suite; an error does not
Answer: A failure is an assertion that did not hold; an error is an unexpected exception in the test. A failure means an assertion was false; an error means the test code itself raised an unexpected exception. The result reports them separately.
Which method tells you whether a whole test run passed?
- result.passed()
- result.isOk()
- result.success
- result.wasSuccessful()
Answer: result.wasSuccessful(). result.wasSuccessful() returns True only if there were no failures or errors in the run.
Which assertion passes when its argument is truthy?
- assertEqual(x)
- assertTrue(x)
- assertTruthy(x)
- assertBool(x)
Answer: assertTrue(x). assertTrue(x) passes when x is truthy — handy for boolean conditions like isinstance checks.
How does unittest differ from pytest in writing assertions?
- unittest uses plain assert; pytest uses self.assertEqual
- Both require @pytest.fixture
- unittest uses self.assertEqual(a, b); pytest uses the plain assert statement
- Neither supports fixtures
Answer: unittest uses self.assertEqual(a, b); pytest uses the plain assert statement. unittest is class-based with self.assert* methods; pytest lets you write plain functions and use the normal assert statement with less boilerplate.
Continue this course
- Previous: Hashing with hashlib
- Next: Advanced Type Hints (Generics, Protocol, TypedDict) — Generics with TypeVar, structural typing with Protocol, and TypedDict
- Quick reference: Python cheat sheet
Frequently asked questions
What is unittest and why should I write tests?
unittest is Python's built-in testing framework. You write small functions that call your code and assert that it produces the expected result; the framework runs them all and reports which passed and which failed. Tests catch bugs the moment you introduce them, document how your code is supposed to behave, and let you change code fearlessly because a green test suite proves you did not break anything.
What do setUp and tearDown do?
setUp runs automatically before every single test method, and tearDown runs after each one. setUp is where you build the fixtures a test needs — a fresh object, a temporary file, a sample database — so each test starts from a clean, identical state. tearDown cleans those up afterward. Because they run per-test, no test can accidentally depend on the leftovers of another.
What is the difference between assertEqual, assertTrue, and assertRaises?
assertEqual(a, b) passes when a equals b and is the assertion you will use most. assertTrue(x) passes when x is truthy, which is handy for boolean conditions. assertRaises checks that a block of code raises a specific exception — you use it with a 'with self.assertRaises(ValueError):' block to confirm your code fails correctly on bad input. There are many more (assertIn, assertIsNone, assertAlmostEqual), but these three cover most cases.
Should I use unittest or pytest?
unittest ships with Python and uses a class-based style with self.assertEqual methods, so it is always available and familiar to anyone who has used JUnit-style frameworks. pytest is a popular third-party tool that lets you write plain functions and use the normal assert statement, with less boilerplate and richer output. Many teams prefer pytest for new projects, but the testing concepts — fixtures, assertions, isolation — are identical, so learning unittest first transfers directly.