pytest is the testing framework that makes Python tests a joy to write — if you know its features. Most developers use pytest as a basic test runner and miss fixtures with scope, parametrize for data-driven tests, conftest.py for shared setup, and the patching patterns that make external dependencies testable.
⚡ TL;DR: Fixtures for reusable setup/teardown with scope control. @pytest.mark.parametrize for data-driven tests. conftest.py for shared fixtures across test files. unittest.mock.patch for external dependencies. pytest-cov for coverage. pytest.raises for exception testing.
Fixtures — reusable test setup
import pytest
# Function scope (default): new fixture per test
@pytest.fixture
def user_data():
return {'name':'Alice','email':'alice@example.com','age':30}
# Module scope: created once per module
@pytest.fixture(scope='module')
def db_connection():
conn = create_test_db()
yield conn # Provide to test
conn.close() # Cleanup after ALL tests in module
# Session scope: created once for entire test session
@pytest.fixture(scope='session')
def app_client():
from myapp import create_app
app = create_app({'TESTING':True,'DB':'sqlite:///:memory:'})
with app.test_client() as client:
yield client
# Using fixtures:
def test_create_user(db_connection, user_data):
result = db_connection.create_user(**user_data)
assert result.id is not None
assert result.email == user_data['email']
parametrize — data-driven tests
@pytest.mark.parametrize('email,valid', [
('alice@example.com', True),
('bob@test.co.uk', True),
('notanemail', False),
('@nodomain.com', False),
('', False),
('a' * 250 + '@example.com', False), # Too long
])
def test_email_validation(email, valid):
assert validate_email(email) == valid
# Multiple parameters:
@pytest.mark.parametrize('a,b,expected', [
(1, 2, 3), (0, 0, 0), (-1, 1, 0), (100, -1, 99)
])
def test_add(a, b, expected):
assert add(a, b) == expected
# Generate 6 test cases from one parametrize — efficient!
Mocking external dependencies
from unittest.mock import patch, Mock, AsyncMock
def test_send_email_on_user_creation():
with patch('myapp.email_service.send') as mock_send:
mock_send.return_value = {'status':'sent','id':'msg-123'}
result = create_user({'name':'Alice','email':'alice@example.com'})
assert result.id is not None
mock_send.assert_called_once_with(
to='alice@example.com',
template='welcome'
)
# Async mocking:
async def test_fetch_user():
with patch('myapp.http_client.get', new_callable=AsyncMock) as mock_get:
mock_get.return_value = Mock(json=AsyncMock(return_value={'id':1,'name':'Alice'}))
user = await fetch_user(1)
assert user['name'] == 'Alice'
conftest.py and advanced patterns
# conftest.py — shared across test files, no import needed
import pytest
from myapp import create_app
from myapp.database import db
@pytest.fixture(scope='session')
def app():
app = create_app({'TESTING':True,'SQLALCHEMY_DATABASE_URI':'sqlite:///:memory:'})
with app.app_context():
db.create_all()
yield app
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture(autouse=True) # Applied to ALL tests automatically
def reset_db(app):
yield
with app.app_context():
db.session.rollback() # Reset after each test
# pytest.ini — configure coverage:
[pytest]
addopts = --cov=myapp --cov-report=term-missing --cov-fail-under=80
- ✅ Function scope fixtures for test isolation, session scope for expensive setup
- ✅ parametrize for data-driven tests — test many cases in one function
- ✅ conftest.py for shared fixtures — no import needed
- ✅ autouse=True for fixtures that apply to all tests (DB reset)
- ✅ AsyncMock for async functions
- ❌ Never use real external services in unit tests — always mock
- ❌ Never share mutable state between tests — causes order-dependent failures
External reference: pytest documentation.
Recommended Reading
→ Designing Data-Intensive Applications — The bible of distributed systems and production engineering at scale.
→ The Pragmatic Programmer — Timeless engineering wisdom every senior developer needs.
Affiliate links. We earn a small commission at no extra cost to you.
Free Weekly Newsletter
🚀 Join 2,000+ Senior Developers
Get expert-level JavaScript, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
