After switching from unittest to pytest, the thing I noticed most wasn’t some killer feature β it was not having to remember all the assertXxx methods.
Just write assert result == expected. pytest knows how to expand the failure message on its own.
Why pytest Instead of unittest
unittest ships with the standard library, no install needed, but it has some rough edges:
- Tests must inherit from
TestCaseβ you can’t just write plain functions - You need
self.assertEqual,self.assertIn,self.assertRaises⦠hard to keep track of setUp/tearDownscope is fixed at the class level, not flexible
Three things in pytest that made me not go back:
- Plain assert β failures show the actual values automatically
- Fixtures injected on demand β scope can be function / class / module / session
- parametrize β test multiple inputs with one decorator
Install
| |
The Simplest Test
| |
| |
On failure:
| |
No guessing which value is which β pytest expands it.
Fixtures: Better Than setUp
unittest’s setUp runs before every test with fixed class scope.
pytest fixtures let you control scope and share across modules:
| |
Setup before yield, teardown after. Much cleaner.
Scope
| |
| scope | lifetime |
|---|---|
function | default β rebuilt for every test |
class | shared within a class |
module | shared within a file |
session | shared for the entire test run |
I typically set database connections to session scope, with each test function running inside its own transaction that rolls back. The full test suite runs without being painfully slow.
conftest.py
Fixtures in conftest.py are available to all test files in the same directory β no imports needed:
| |
| |
Both test_users.py and test_orders.py can use admin_user without importing anything.
parametrize: Multiple Inputs at Once
| |
Each set of inputs runs as a separate test. On failure, it tells you exactly which input set broke:
| |
I use this for boundary conditions β normal values, zero, negatives, extremes all in one go.
Testing Exceptions
| |
To also check the exception message:
| |
Running Only Some Tests
| |
--lf (last failed) is what I reach for most. Fix a bug, immediately re-run just the tests that were failing β no need to wait through the whole suite.
Useful Options
| |
During development I almost always add -x β one failure at a time, output doesn’t get buried.
Summary
The gap between pytest and unittest isn’t about features β it’s about how comfortable the writing experience is. Plain assert, on-demand fixture composition, parametrize for multiple inputs. Once those habits are in place, testing stops feeling like something that requires opening documentation every time.
If your tests need a lot of fake data, polyfactory generates it from type hints automatically β no hand-crafting fixture data.
