What are the key differences between unittest and pytest?
Both are Python testing frameworks, but they differ significantly in philosophy and syntax:
unittest (standard library)
- Class-based: tests must be methods in a
TestCasesubclass - Uses
self.assertEqual(),self.assertRaises(), etc. - setUp/tearDown for fixtures
- Verbose, Java-like style
- Built into Python, no extra install
pytest (third-party)
- Function-based: plain functions with
assertstatements - Automatic test discovery (files/functions starting with
test_) - Rich fixture system with dependency injection
- Powerful parametrize for data-driven tests
- Better error messages (assertion introspection)
- Huge plugin ecosystem
- Can run unittest tests too
When to use which:
- pytest: Almost always preferred for new projects
- unittest: Legacy codebases, or when you cannot install third-party packages
unittest style:
class TestMath(TestCase):
def test_add(self):
self.assertEqual(add(1, 2), 3)
pytest style:
def test_add():
assert add(1, 2) == 3
# unittest style
import unittest
class TestCalculator(unittest.TestCase):
def setUp(self):
self.calc = Calculator()
def test_add(self):
self.assertEqual(self.calc.add(1, 2), 3)
def test_divide_by_zero(self):
with self.assertRaises(ZeroDivisionError):
self.calc.divide(1, 0)
def tearDown(self):
del self.calc
# pytest style (equivalent, much simpler)
class Calculator:
def add(self, a, b):
return a + b
def divide(self, a, b):
return a / b
import pytest
def test_add():
calc = Calculator()
assert calc.add(1, 2) == 3
def test_divide_by_zero():
calc = Calculator()
with pytest.raises(ZeroDivisionError):
calc.divide(1, 0)
# pytest with fixtures (dependency injection)
@pytest.fixture
def calc():
return Calculator()
def test_add_with_fixture(calc):
assert calc.add(1, 2) == 3
# pytest parametrize (data-driven tests)
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300),
])
def test_add_parametrized(calc, a, b, expected):
assert calc.add(a, b) == expected
# pytest better error messages
def test_list_comparison():
result = [1, 2, 3, 4]
expected = [1, 2, 5, 4]
assert result == expected
# pytest shows: [1, 2, 3, 4] != [1, 2, 5, 4]
# At index 2: 3 != 5
- •pytest uses plain assert statements; unittest uses self.assert* methods
- •pytest fixtures provide dependency injection; unittest uses setUp/tearDown
- •pytest has parametrize for data-driven tests out of the box
- •pytest can run unittest tests, providing an easy migration path
- •pytest must be installed (pip install pytest); unittest is built-in
- •unittest requires test classes; pytest allows plain functions (simpler)
- •Mixing pytest fixtures with unittest TestCase classes has limitations
- •pytest's assert rewriting does not work inside helper functions by default (use pytest.register_assert_rewrite)