Why Choose pytest over unittest?
Key differences
Section titled “Key differences”unittest
Section titled “unittest”- class-based (
unittest.TestCase) - assertion methods (
self.assertEqual) - more boilerplate
pytest
Section titled “pytest”- simple functions
- plain
assert - powerful fixtures
- rich plugin ecosystem
Example comparison
Section titled “Example comparison”unittest
Section titled “unittest”import unittest
def add(a, b):
return a + b
class TestAdd(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 3), 5)pytest
Section titled “pytest”
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5Why teams like pytest
Section titled “Why teams like pytest”- less ceremony
- clearer failures
- fixtures scale better than setUp/tearDown
- easy parameterization
🧪 Try It Yourself
Section titled “🧪 Try It Yourself”Exercise 1 – Write a unittest TestCase
Section titled “Exercise 1 – Write a unittest TestCase”Exercise 2 – assertRaises
Section titled “Exercise 2 – assertRaises”Exercise 3 – setUp and tearDown
Section titled “Exercise 3 – setUp and tearDown”The same test, both frameworks
Section titled “The same test, both frameworks”import unittest
from shop import price_with_tax
class TestPricing(unittest.TestCase):
def test_tax(self):
self.assertEqual(price_with_tax(100), 120.0)from shop import price_with_tax
def test_tax():
assert price_with_tax(100) == 120.0No class, no self, no assertion vocabulary. What makes that possible is assertion
rewriting — pytest rewrites the bytecode of assert statements at import time so it can
report the operands:
> assert price_with_tax(100) == 121.0
E assert 120.0 == 121.0
E + where 120.0 = price_with_tax(100)It reported the comparison and the sub-expression that produced the left-hand side.
The same plain assert under unittest measured only AssertionError, with no values at
all.
flowchart TD U["unittest"] --> UA["plain assert -> 'AssertionError', no values"] UA --> UV["so it needs assertEqual, assertIn, assertAlmostEqual, ..."] P["pytest"] --> PA["rewrites assert at import time"] PA --> PV["plain assert reports both operands and sub-expressions"]
Running the same test suite
Section titled “Running the same test suite”pytest runs unittest.TestCase classes unchanged. Adopting it does not mean rewriting
anything — you install it, run pytest, and your existing suite executes with better
failure output. New tests can then be written as plain functions.
What you actually gain
Section titled “What you actually gain”| unittest | pytest | |
|---|---|---|
| in the standard library | yes | no, pip install pytest |
plain assert reports values | no | yes |
| many inputs | subTest | @parametrize, each case its own test |
| shared setup | setUp per class | fixtures, requested by name and scoped |
| selecting tests | by name | -k expressions and -m markers |
| plugins | few | a large ecosystem: coverage, HTML reports, xdist |
| runs the other’s tests | no | yes |
Measured on a small suite: five test functions became 8 tests, because one was parametrized with four cases and each case is reported separately.
When to stay with unittest
Section titled “When to stay with unittest”- A third-party dependency is genuinely unacceptable — pytest is not in the standard library.
- The codebase is large, stable, and nobody is complaining about the tests.
Otherwise the cost is one install and the benefit starts with the first failure you have to diagnose.
See it move
Section titled “See it move”Check yourself
Section titled “Check yourself”-
How can pytest report operand values from a plain assert when unittest cannot?
Measured pytest printing assert 120.0 == 121.0 plus where 120.0 = price_with_tax(100). The same plain assert under unittest printed only AssertionError.
pch.quizShowAnswer
B — it rewrites the bytecode of assert statements when it imports your test module — Measured pytest printing assert 120.0 == 121.0 plus where 120.0 = price_with_tax(100). The same plain assert under unittest printed only AssertionError.
-
What does adopting pytest require you to do with an existing unittest suite?
Install it, run pytest, and the existing suite executes with better failure output. New tests can be plain functions from then on.
pch.quizShowAnswer
B — nothing; pytest runs unittest.TestCase classes unchanged — Install it, run pytest, and the existing suite executes with better failure output. New tests can be plain functions from then on.
-
Five test functions produced 8 tests in the measured run. Why?
parametrize expands one function into independently named and reported tests, which is what makes a failing case identifiable.
pch.quizShowAnswer
B — one function was parametrized with four cases, and each case is a separate test — parametrize expands one function into independently named and reported tests, which is what makes a failing case identifiable.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading