← Назад к вопросам
Для чего используешь фикстуры?
1.3 Junior🔥 191 комментариев
#Фреймворки тестирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Фикстуры в тестировании
Фикстуры — один из самых мощных инструментов в pytest. Это функции, которые подготавливают данные и состояние для тестов, а также чистят их после выполнения.
Определение
Фикстура — это функция, помеченная декоратором @pytest.fixture, которая:
- Подготавливает ресурсы перед тестом (setup)
- Предоставляет эти ресурсы тесту
- Очищает ресурсы после теста (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()
Заключение
Фикстуры используют для:
- DRY — не дублировать код setup в каждом тесте
- Чистоты — автоматически очищать ресурсы (teardown)
- Переиспользования — одна фикстура используется многими тестами
- Зависимостей — фикстуры могут зависеть от других фикстур
- Производительности — через scope можно кэшировать дорогие ресурсы
Без фикстур тесты становятся грязными и нелёгкими в поддержке. С фикстурами — код становится чистым, модульным и переиспользуемым.