← Назад к вопросам
Как добавлял фикстуры в тесты?
2.0 Middle🔥 241 комментариев
#Python Core#Тестирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Фикстуры в pytest
Фикстуры — это функции в pytest, которые подготавливают данные и ресурсы перед тестами. Они позволяют избежать дублирования кода в тестах и обеспечивают чистоту тестового окружения.
Базовая фикстура
import pytest
from sqlalchemy.orm import Session
from app.models import User
from app.database import get_db
# Простая фикстура
@pytest.fixture
def sample_user():
return {'id': 1, 'name': 'John', 'email': 'john@example.com'}
def test_user_creation(sample_user):
assert sample_user['name'] == 'John'
assert sample_user['email'] == 'john@example.com'
Фикстуры с setup и teardown
@pytest.fixture
def database_session():
"""Фикстура для работы с БД"""
# Setup: создаём сессию
session = Session()
yield session # Передаём в тест
# Teardown: откатываем транзакцию и закрываем
session.rollback()
session.close()
def test_save_user(database_session):
user = User(name='Alice', email='alice@test.com')
database_session.add(user)
database_session.commit()
result = database_session.query(User).filter_by(email='alice@test.com').first()
assert result.name == 'Alice'
Области видимости (scopes) фикстур
# function scope (по умолчанию) — создаётся для каждого теста
@pytest.fixture(scope='function')
def user_fixture():
return {'id': 1}
# module scope — создаётся один раз на весь модуль
@pytest.fixture(scope='module')
def database_connection():
conn = create_connection()
yield conn
conn.close()
# session scope — создаётся один раз на всю сессию тестирования
@pytest.fixture(scope='session')
def app():
app = create_app()
app.config['TESTING'] = True
return app
# class scope — создаётся один раз на весь класс тестов
@pytest.fixture(scope='class')
def class_db():
db = Database()
yield db
db.cleanup()
Параметризованные фикстуры
import pytest
from typing import List
# Фикстура с несколькими вариантами
@pytest.fixture(params=['sqlite', 'postgresql', 'mongodb'])
def database_type(request):
return request.param
def test_connection(database_type):
"""Тест запустится 3 раза с разными БД"""
print(f'Testing with {database_type}')
assert database_type in ['sqlite', 'postgresql', 'mongodb']
# Параметризация с ID
@pytest.fixture(
params=[
pytest.param({'name': 'Alice', 'age': 25}, id='adult'),
pytest.param({'name': 'Bob', 'age': 17}, id='minor'),
]
)
def user_data(request):
return request.param
def test_user_age(user_data):
if user_data['age'] >= 18:
assert user_data['name'] in ['Alice']
else:
assert user_data['name'] in ['Bob']
Зависимости между фикстурами
from fastapi.testclient import TestClient
@pytest.fixture
def app():
"""Создаём FastAPI приложение"""
app = FastAPI()
@app.get('/users/{user_id}')
def get_user(user_id: int):
return {'id': user_id, 'name': 'John'}
return app
@pytest.fixture
def client(app):
"""Создаём тестовый клиент на основе app"""
return TestClient(app)
@pytest.fixture
def authenticated_client(client):
"""Авторизованный клиент"""
client.headers['Authorization'] = 'Bearer token123'
return client
def test_get_user(authenticated_client):
response = authenticated_client.get('/users/1')
assert response.status_code == 200
assert response.json()['name'] == 'John'
conftest.py — глобальные фикстуры
# tests/conftest.py — файл, где определяются общие фикстуры
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.database import Base
from app.models import User
@pytest.fixture(scope='session')
def test_db():
"""БД для всех тестов"""
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
return engine
@pytest.fixture(scope='function')
def db_session(test_db):
"""Сессия БД для каждого теста"""
Session = sessionmaker(bind=test_db)
session = Session()
yield session
session.rollback()
session.close()
@pytest.fixture
def user_factory(db_session):
"""Factory для создания пользователей"""
def _create_user(name='Test User', email='test@example.com'):
user = User(name=name, email=email)
db_session.add(user)
db_session.commit()
return user
return _create_user
# tests/test_users.py
def test_create_user(user_factory):
user = user_factory(name='Alice', email='alice@test.com')
assert user.id is not None
assert user.name == 'Alice'
def test_multiple_users(user_factory):
user1 = user_factory(name='Alice')
user2 = user_factory(name='Bob')
assert user1.name != user2.name
Фикстуры с контекстом (async)
import pytest
import asyncio
from httpx import AsyncClient
from fastapi import FastAPI
@pytest.fixture
async def async_client():
"""Фикстура для асинхронного клиента"""
app = FastAPI()
@app.get('/async-endpoint')
async def async_endpoint():
return {'status': 'ok'}
async with AsyncClient(app=app, base_url='http://test') as client:
yield client
@pytest.mark.asyncio
async def test_async_endpoint(async_client):
response = await async_client.get('/async-endpoint')
assert response.status_code == 200
assert response.json()['status'] == 'ok'
Fixtures и Mocking
from unittest.mock import Mock, patch
import pytest
@pytest.fixture
def mock_external_api():
"""Mock для внешнего API"""
with patch('app.services.external_api.fetch_data') as mock:
mock.return_value = {'status': 'success', 'data': [1, 2, 3]}
yield mock
def test_fetch_data(mock_external_api):
from app.services import fetch_data
result = fetch_data()
assert result['status'] == 'success'
mock_external_api.assert_called_once()
# Fixture с моком внутри
@pytest.fixture
def redis_mock():
"""Mock для Redis клиента"""
mock = Mock()
mock.get.return_value = 'cached_value'
mock.set.return_value = True
return mock
def test_cache(redis_mock):
redis_mock.set('key', 'value')
assert redis_mock.get('key') == 'cached_value'
redis_mock.set.assert_called_with('key', 'value')
Advanced: Autouse фикстуры
@pytest.fixture(autouse=True)
def reset_env():
"""Автоматически запускается перед каждым тестом"""
import os
original_debug = os.environ.get('DEBUG')
os.environ['DEBUG'] = 'false'
yield
# Restore
if original_debug:
os.environ['DEBUG'] = original_debug
else:
os.environ.pop('DEBUG', None)
def test_something():
# DEBUG уже выключен благодаря autouse фикстуре
pass
Лучшие практики
- Используй conftest.py для общих фикстур в разных модулях
- Минимизируй область видимости (используй 'function' по умолчанию, 'session' только когда нужно)
- Factory fixtures для создания данных с вариациями
- Чистый teardown — откатывай все изменения БД
- Параметризация вместо дублирования тестов
- Типизация — указывай типы параметров фикстур
- Зависимости между фикстурами вместо глобального состояния
Сложный пример: Тестирование с БД
# tests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session as SessionType
from app.database import Base
@pytest.fixture(scope='session')
def db_engine():
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
yield engine
Base.metadata.drop_all(engine)
@pytest.fixture
def db_session(db_engine) -> SessionType:
connection = db_engine.connect()
transaction = connection.begin()
session = sessionmaker(bind=connection)()
yield session
session.close()
transaction.rollback()
connection.close()
# tests/test_users.py
from app.models import User
from app.services import UserService
def test_create_user(db_session):
service = UserService(db_session)
user = service.create('john@example.com', 'password123')
assert user.email == 'john@example.com'
assert user.id is not None
def test_user_not_found(db_session):
service = UserService(db_session)
with pytest.raises(ValueError, match='User not found'):
service.get_by_email('nonexistent@example.com')
Фикстуры — это сердце тестирования. Они делают тесты чище, переиспользуемыми и легче поддерживаемыми. Ключ — правильно организовать область видимости и зависимости.