Testing with pytest
debt(d7/e3/b3/t5)
Closest to 'only careful code review or runtime testing' (d7) — pytest itself runs tests but won't flag 'you should use parametrize here' or 'this fixture scope is too wide'; ruff and pytest-mock listed in detection_hints don't catch test-design smells. Review catches it.
Closest to 'simple parameterised fix' (e3) — quick_fix is replacing duplicated setup with a fixture or rewriting repeated tests with @parametrize; pattern-level refactor within a test file, not architectural.
Closest to 'localised tax' (b3) — testing infrastructure applies across web/cli contexts but lives in the test suite; bad fixture design slows test development but doesn't shape production code.
Closest to 'notable trap most devs eventually learn' (t5) — misconception notes fixtures look complicated coming from unittest, and common_mistakes flag the session-scope-with-mutable-state gotcha which is a classic documented pitfall.
Also Known As
TL;DR
Explanation
pytest discovers tests by naming convention (test_*.py, *_test.py), uses plain assert statements with introspective failure messages, and provides fixtures via dependency injection (function parameters). Key features: @pytest.fixture for reusable setup/teardown, @pytest.mark.parametrize for data-driven tests, conftest.py for shared fixtures, and marks for grouping tests. The plugin ecosystem (pytest-cov, pytest-mock, pytest-asyncio) covers virtually every testing need.
Common Misconception
Why It Matters
Common Mistakes
- Not using fixtures for shared setup — copy-pasting setup code across tests instead of extracting a fixture.
- Fixtures with too wide a scope — session-scoped fixtures with mutable state bleed between tests.
- Not using parametrize for data-driven tests — writing the same test five times with different inputs.
- Not using conftest.py for shared fixtures — defining fixtures in every test file instead of once.
Code Examples
# unittest style — verbose:
import unittest
class TestUserService(unittest.TestCase):
def setUp(self):
self.db = create_test_db()
self.service = UserService(self.db)
def test_create_user_success(self):
result = self.service.create({'email': 'a@b.com', 'name': 'Alice'})
self.assertEqual(result['email'], 'a@b.com') # Less informative failure
# pytest style — concise, with fixtures:
import pytest
@pytest.fixture
def user_service():
db = create_test_db()
yield UserService(db)
db.rollback() # Teardown
@pytest.mark.parametrize('email,name', [
('alice@example.com', 'Alice'),
('bob@example.com', 'Bob'),
])
def test_create_user(user_service, email, name):
result = user_service.create({'email': email, 'name': name})
assert result['email'] == email # pytest shows actual vs expected on failure