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

Для чего используешь фикстуры?

1.3 Junior🔥 191 комментариев
#Фреймворки тестирования

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

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

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

# Фикстуры в тестировании

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

Определение

Фикстура — это функция, помеченная декоратором @pytest.fixture, которая:

  1. Подготавливает ресурсы перед тестом (setup)
  2. Предоставляет эти ресурсы тесту
  3. Очищает ресурсы после теста (teardown)

Основное использование

1. Инициализация объектов

import pytest
from app.models import User
from app.services import UserService

# БЕЗ фикстуры (плохо)
def test_create_user_bad():
    service = UserService()
    user = service.create(name="Alice", email="alice@test.com")
    assert user.name == "Alice"
    # Много кода дублируется в каждом тесте

def test_update_user_bad():
    service = UserService()  # Повторение
    user = User(name="Bob")
    service.save(user)
    assert user.id is not None

# С ФИКСТУРОЙ (хорошо)
@pytest.fixture
def user_service():
    return UserService()

def test_create_user(user_service):
    user = user_service.create(name="Alice", email="alice@test.com")
    assert user.name == "Alice"

def test_update_user(user_service):
    user = User(name="Bob")
    user_service.save(user)
    assert user.id is not None

2. Подготовка данных в БД

import pytest
from sqlalchemy.orm import Session
from app.models import User, Post
from app.database import Base, engine

@pytest.fixture
def test_db():
    # Setup: создаём таблицы
    Base.metadata.create_all(bind=engine)
    db = Session(bind=engine)
    
    yield db  # Тест использует db
    
    # Teardown: удаляем данные
    db.close()
    Base.metadata.drop_all(bind=engine)

@pytest.fixture
def sample_user(test_db):
    """Зависит от другой фикстуры"""
    user = User(name="Alice", email="alice@test.com")
    test_db.add(user)
    test_db.commit()
    test_db.refresh(user)
    return user

def test_create_post(test_db, sample_user):
    post = Post(title="Hello", author_id=sample_user.id)
    test_db.add(post)
    test_db.commit()
    test_db.refresh(post)
    
    assert post.author_id == sample_user.id

3. Настройка Selenium/Playwright драйвера

import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

@pytest.fixture
def driver():
    # Setup: запускаем браузер
    options = webdriver.ChromeOptions()
    options.add_argument('--headless')
    driver = webdriver.Chrome(options=options)
    
    yield driver  # Тест использует драйвер
    
    # Teardown: закрываем браузер
    driver.quit()

@pytest.fixture
def logged_in_driver(driver):
    """Фикстура зависит от другой фикстуры"""
    driver.get("https://example.com/login")
    driver.find_element(By.ID, "username").send_keys("testuser")
    driver.find_element(By.ID, "password").send_keys("password123")
    driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
    
    yield driver  # Теперь драйвер залогинен

def test_user_dashboard(logged_in_driver):
    logged_in_driver.get("https://example.com/dashboard")
    assert "Welcome" in logged_in_driver.page_source

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

Фикстуры могут создаваться один раз и переиспользоваться несколько тестов:

@pytest.fixture(scope="function")  # Default: для каждого теста
def db_connection():
    connection = connect_to_db()
    yield connection
    connection.close()

@pytest.fixture(scope="class")  # Один раз для всего класса
def expensive_setup():
    return ExpensiveObject()

@pytest.fixture(scope="module")  # Один раз для всего модуля
def test_data():
    return load_test_data()

@pytest.fixture(scope="session")  # Один раз для всей сессии тестов
def docker_container():
    container = start_docker("postgres:latest")
    yield container
    container.stop()

@pytest.fixture(scope="session", autouse=True)  # Автоматически
def setup_logging():
    logging.basicConfig(level=logging.DEBUG)
    yield
    logging.shutdown()

Когда использовать какой scope:

  • function — когда каждому тесту нужно чистое состояние (по умолчанию)
  • class — когда группа тестов может переиспользовать данные
  • module — когда есть дорогостоящее подключение (БД, API)
  • session — когда нужно один раз инициализировать что-то на весь прогон
@pytest.fixture(scope="module")
def api_client():
    """Один клиент для всех тестов в модуле"""
    return APIClient(base_url="https://api.test.com")

def test_get_users(api_client):
    response = api_client.get("/users")
    assert response.status_code == 200

def test_create_user(api_client):
    response = api_client.post("/users", data={"name": "Alice"})
    assert response.status_code == 201

Parametrize с фикстурами

@pytest.fixture
def user_roles():
    return ["admin", "user", "guest"]

@pytest.mark.parametrize("role", ["admin", "user", "guest"])
def test_access_levels(role):
    assert has_permission(role, "view_dashboard")

# Более сложный пример
@pytest.fixture
def test_cases():
    return [
        (10, 20, 30),      # a, b, expected
        (5, -5, 0),
        (100, 100, 200),
    ]

@pytest.mark.parametrize("a,b,expected", [
    (10, 20, 30),
    (5, -5, 0),
    (100, 100, 200),
])
def test_addition(a, b, expected):
    assert a + b == expected

Полезные встроенные фикстуры pytest

import pytest
import tempfile
import os

def test_with_tmp_path(tmp_path):
    """tmp_path — временная директория"""
    test_file = tmp_path / "test.txt"
    test_file.write_text("Hello")
    assert test_file.read_text() == "Hello"

def test_with_capsys(capsys):
    """capsys — захват stdout/stderr"""
    print("Hello, World!")
    captured = capsys.readouterr()
    assert "Hello" in captured.out

def test_with_monkeypatch(monkeypatch):
    """monkeypatch — временное изменение функций/переменных"""
    def mock_get_api_key():
        return "test-key-12345"
    
    monkeypatch.setattr("app.config.get_api_key", mock_get_api_key)
    assert app.config.get_api_key() == "test-key-12345"

def test_with_caplog(caplog):
    """caplog — захват логов"""
    logger = logging.getLogger(__name__)
    logger.warning("Test warning")
    assert "Test warning" in caplog.text

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

Фикстуры, которые нужны во многих тестах, кладут в conftest.py:

# tests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
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(bind=engine)
    
    yield Session(bind=engine)
    
    Base.metadata.drop_all(bind=engine)

@pytest.fixture
def admin_user(test_db):
    """Admin пользователь в каждом тесте"""
    user = User(name="Admin", email="admin@test.com", is_admin=True)
    test_db.add(user)
    test_db.commit()
    test_db.refresh(user)
    return user

@pytest.fixture
def regular_user(test_db):
    """Обычный пользователь в каждом тесте"""
    user = User(name="User", email="user@test.com", is_admin=False)
    test_db.add(user)
    test_db.commit()
    test_db.refresh(user)
    return user
# tests/test_users.py
def test_admin_can_delete_user(admin_user, regular_user, test_db):
    """Фикстуры автоматически подгружаются из conftest.py"""
    assert admin_user.is_admin == True
    assert regular_user.is_admin == False
    
    # Удаляем пользователя как админ
    test_db.delete(regular_user)
    test_db.commit()
    
    # Проверяем что пользователь удалён
    assert test_db.query(User).filter_by(id=regular_user.id).first() is None

Практический пример: E2E тесты с Playwright

# tests/conftest.py
import pytest
from playwright.sync_api import sync_playwright
from app.main import app
from fastapi.testclient import TestClient

@pytest.fixture(scope="session")
def browser():
    """Браузер для всей сессии"""
    with sync_playwright() as p:
        browser = p.chromium.launch()
        yield browser
        browser.close()

@pytest.fixture
def page(browser):
    """Новая страница для каждого теста"""
    page = browser.new_page()
    yield page
    page.close()

@pytest.fixture
def test_api():
    """API клиент для подготовки данных"""
    return TestClient(app)

@pytest.fixture
def authenticated_page(page, test_api):
    """Залогиненная страница"""
    # Подготовка данных через API
    response = test_api.post("/api/v1/dev/test-auth", json={
        "user_id": "test-user-123"
    })
    
    # Перейти на страницу и залогиниться
    page.goto("http://localhost:3000")
    page.evaluate(f"""
        localStorage.setItem('auth_token', '{response.json()["token"]}')
    """)
    page.reload()
    
    yield page

# tests/test_checkout.py
def test_user_can_complete_purchase(authenticated_page, test_api):
    """Полный сценарий покупки"""
    page = authenticated_page
    
    # Поиск товара
    page.goto("http://localhost:3000/products")
    page.click("text=Laptop")
    
    # Добавление в корзину
    page.click("button:has-text('Add to Cart')")
    
    # Оформление
    page.click("a:has-text('Go to Checkout')")
    page.click("button:has-text('Place Order')")
    
    # Подтверждение
    page.wait_for_url("**/order-confirmation")
    assert "Thank you" in page.text_content()

Заключение

Фикстуры используют для:

  1. DRY — не дублировать код setup в каждом тесте
  2. Чистоты — автоматически очищать ресурсы (teardown)
  3. Переиспользования — одна фикстура используется многими тестами
  4. Зависимостей — фикстуры могут зависеть от других фикстур
  5. Производительности — через scope можно кэшировать дорогие ресурсы

Без фикстур тесты становятся грязными и нелёгкими в поддержке. С фикстурами — код становится чистым, модульным и переиспользуемым.