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

Что такое фикстура?

3.0 Senior🔥 181 комментариев
#FastAPI и Flask#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Фикстура (Fixture)

Фикстура — это подготовленное состояние данных или окружение для тестирования. Фикстура предоставляет ресурсы, которые нужны тестам для выполнения (базовые данные, подключения к БД, моки объектов и т.д.). В контексте pytest фикстура — это функция, которая подготавливает данные перед тестом и может очищать ресурсы после теста.

Основная концепция

import pytest

# Простая фикстура
@pytest.fixture
def simple_data():
    return {'name': 'John', 'age': 30}

# Тест использует фикстуру
def test_user_data(simple_data):
    assert simple_data['name'] == 'John'
    assert simple_data['age'] == 30

# Фикстура автоматически вызывается и передаётся в тест

Setup и Teardown

Фикстуры могут подготавливать ресурсы (setup) и очищать их (teardown):

import pytest

@pytest.fixture
def database():
    # SETUP: подготовка
    print('Подключение к БД')
    db_connection = connect_to_database('localhost:5432')
    db_connection.create_table('users')
    
    # Передаём ресурс в тест
    yield db_connection
    
    # TEARDOWN: очистка
    print('Закрытие соединения')
    db_connection.close()

def test_database_operations(database):
    # База данных готова
    database.insert('users', {'name': 'Alice'})
    assert database.count('users') == 1
    # После теста автоматически вызовется teardown

Реальный пример: тестирование API

import pytest
from fastapi.testclient import TestClient
from app import create_app

# Фикстура для тестового приложения
@pytest.fixture
def test_app():
    app = create_app()
    app.config['TESTING'] = True
    return app

# Фикстура для тестового клиента
@pytest.fixture
def client(test_app):
    return TestClient(test_app)

# Фикстура для создания тестовых данных
@pytest.fixture
def test_user(client):
    user_data = {'name': 'John', 'email': 'john@example.com'}
    response = client.post('/users', json=user_data)
    return response.json()

# Тест использует фикстуры
def test_get_user(client, test_user):
    response = client.get(f'/users/{test_user["id"]}')
    assert response.status_code == 200
    assert response.json()['name'] == 'John'

def test_update_user(client, test_user):
    update_data = {'name': 'Jane'}
    response = client.put(f'/users/{test_user["id"]}', json=update_data)
    assert response.status_code == 200
    assert response.json()['name'] == 'Jane'

Фикстуры с параметризацией

import pytest

# Фикстура, которая возвращает разные значения
@pytest.fixture(params=[1, 5, 10, 100])
def list_size(request):
    return request.param

# Тест будет запущен 4 раза с разными значениями
def test_list_creation(list_size):
    my_list = list(range(list_size))
    assert len(my_list) == list_size

# Результат:
# test_list_creation[1] PASSED
# test_list_creation[5] PASSED
# test_list_creation[10] PASSED
# test_list_creation[100] PASSED

Области видимости фикстур (Scopes)

import pytest

# function scope (по умолчанию) - для каждого теста
@pytest.fixture(scope='function')
def user_function_scope():
    print('Creating user for test')
    return {'id': 1, 'name': 'John'}

# class scope - общая для всех тестов в классе
@pytest.fixture(scope='class')
def database_class_scope():
    print('Connecting to database')
    yield database_connection
    print('Closing database')

# module scope - общая для всех тестов в модуле
@pytest.fixture(scope='module')
def config_module_scope():
    return load_config('test_config.yaml')

# session scope - одна на всю сессию тестирования
@pytest.fixture(scope='session')
def expensive_resource():
    print('Initializing expensive resource once')
    return expensive_initialization()

class TestDatabase:
    def test_query1(self, database_class_scope):
        # database_class_scope используется
        pass
    
    def test_query2(self, database_class_scope):
        # Одно и то же подключение
        pass

Зависимости между фикстурами

import pytest

# Фикстуры могут зависеть друг от друга
@pytest.fixture
def user():
    return {'id': 1, 'name': 'Alice'}

@pytest.fixture
def post(user):
    # Зависит от фикстуры user
    return {
        'id': 101,
        'title': 'My Post',
        'author_id': user['id']
    }

@pytest.fixture
def comment(post, user):
    # Зависит от двух фикстур
    return {
        'id': 1001,
        'text': 'Great post!',
        'post_id': post['id'],
        'author_id': user['id']
    }

def test_comment_structure(comment):
    assert comment['post_id'] == 101
    assert comment['author_id'] == 1

Практический пример: тестирование с БД

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base, User

# Фикстура: тестовая БД
@pytest.fixture
def db_session():
    # Создаём in-memory SQLite БД
    engine = create_engine('sqlite:///:memory:')
    Base.metadata.create_all(engine)  # Создаём таблицы
    
    Session = sessionmaker(bind=engine)
    session = Session()
    
    yield session
    
    # Очистка
    session.close()

# Фикстура: тестовый пользователь
@pytest.fixture
def test_user(db_session):
    user = User(name='John', email='john@example.com')
    db_session.add(user)
    db_session.commit()
    return user

# Тесты
def test_user_creation(db_session):
    user = User(name='Alice', email='alice@example.com')
    db_session.add(user)
    db_session.commit()
    
    found = db_session.query(User).filter_by(name='Alice').first()
    assert found.email == 'alice@example.com'

def test_user_retrieval(db_session, test_user):
    found = db_session.query(User).filter_by(id=test_user.id).first()
    assert found.name == 'John'

def test_user_update(db_session, test_user):
    test_user.name = 'Jane'
    db_session.commit()
    
    found = db_session.query(User).filter_by(id=test_user.id).first()
    assert found.name == 'Jane'

Фикстуры с autouse

import pytest

# Фикстура автоматически используется для всех тестов
@pytest.fixture(autouse=True)
def reset_global_state():
    # Подготовка
    global_var = 0
    yield
    # Очистка - выполняется после каждого теста
    print('Resetting global state')

def test_something():
    # reset_global_state вызовется автоматически
    assert True

def test_another():
    # reset_global_state вызовется и здесь
    assert True

Конфигурация фикстур в conftest.py

# conftest.py - специальный файл pytest
# Фикстуры здесь доступны для всех тестов в проекте

import pytest
import tempfile
import os

@pytest.fixture(scope='session')
def test_config():
    return {
        'database_url': 'sqlite:///:memory:',
        'api_url': 'http://localhost:8000',
        'debug': True
    }

@pytest.fixture
def temp_dir():
    # Создаём временную директорию
    with tempfile.TemporaryDirectory() as tmpdir:
        yield tmpdir
        # Автоматически удаляется после теста

@pytest.fixture
def json_file(temp_dir):
    filepath = os.path.join(temp_dir, 'test.json')
    with open(filepath, 'w') as f:
        f.write('{"key": "value"}')
    yield filepath

Сравнение: старый setup/teardown vs новые фикстуры

# СТАРЫЙ СПОСОБ (unittest)
class TestExample(unittest.TestCase):
    def setUp(self):
        # Выполняется перед каждым тестом
        self.data = [1, 2, 3]
    
    def tearDown(self):
        # Выполняется после каждого теста
        self.data = None
    
    def test_something(self):
        assert len(self.data) == 3

# НОВЫЙ СПОСОБ (pytest fixtures)
@pytest.fixture
def data():
    # Setup
    data = [1, 2, 3]
    yield data
    # Teardown (опционально)

def test_something(data):
    assert len(data) == 3

# Преимущества фикстур:
# 1. Более читаемо
# 2. Зависимости явные
# 3. Можно использовать разные области видимости
# 4. Параметризация легче

Фикстуры для моков

import pytest
from unittest.mock import Mock, patch

# Фикстура с mock объектом
@pytest.fixture
def mock_database():
    mock = Mock()
    mock.query.return_value = [{'id': 1, 'name': 'John'}]
    return mock

@pytest.fixture
def mock_api():
    with patch('requests.get') as mock_get:
        mock_get.return_value.json.return_value = {'status': 'ok'}
        yield mock_get

def test_with_mock_db(mock_database):
    result = mock_database.query()
    assert len(result) == 1

def test_with_mock_api(mock_api):
    # API мок используется в этом тесте
    assert True

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

import pytest

# ХОРОШО: понятное имя, одна ответственность
@pytest.fixture
def authenticated_user():
    user = User(name='John', is_authenticated=True)
    return user

# ХОРОШО: явное описание через docstring
@pytest.fixture
def database():
    """Инициализирует in-memory SQLite для тестов"""
    engine = create_engine('sqlite:///:memory:')
    yield engine
    engine.dispose()

# ПЛОХО: фикстура делает слишком много
@pytest.fixture
def everything():
    db = create_database()
    api = init_api()
    config = load_config()
    return (db, api, config)

# ХОРОШО: разные фикстуры для разных вещей
@pytest.fixture
def database():
    return create_database()

@pytest.fixture
def api():
    return init_api()

@pytest.fixture
def config():
    return load_config()

Вывод

Фикстура — это ключевой механизм в pytest для подготовки тестового окружения:

  • Инициализирует данные и ресурсы перед тестом
  • Очищает ресурсы после теста (yield pattern)
  • Может зависеть от других фикстур
  • Поддерживает разные области видимости
  • Делает тесты чище, читабельнее и переиспользуемыми

Фикстуры — это одна из самых мощных и полезных фич pytest, которые позволяют писать чистые и поддерживаемые тесты.

Что такое фикстура? | PrepBro