← Назад к вопросам

Как добавлял фикстуры в тесты?

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

Лучшие практики

  1. Используй conftest.py для общих фикстур в разных модулях
  2. Минимизируй область видимости (используй 'function' по умолчанию, 'session' только когда нужно)
  3. Factory fixtures для создания данных с вариациями
  4. Чистый teardown — откатывай все изменения БД
  5. Параметризация вместо дублирования тестов
  6. Типизация — указывай типы параметров фикстур
  7. Зависимости между фикстурами вместо глобального состояния

Сложный пример: Тестирование с БД

# 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')

Фикстуры — это сердце тестирования. Они делают тесты чище, переиспользуемыми и легче поддерживаемыми. Ключ — правильно организовать область видимости и зависимости.