@pytest.fixture, scope фикстур, yield для cleanup, conftest.py
Вы уже умеете группировать тесты маркерами и запускать с разными данными. Но что если один и тот же setup повторяется в 10 тестах?
Цель: научиться использовать фикстуры для переиспользования setup-кода и избавиться от дублирования.
# ❌ ПЛОХО — повторяем создание account в каждом тесте
def test_deposit():
account = BankAccount(100) # Дублирование
account.deposit(50)
assert account.balance == 150
def test_withdraw():
account = BankAccount(100) # Дублирование
account.withdraw(30)
assert account.balance == 70
def test_multiple_deposits():
account = BankAccount(100) # Дублирование
account.deposit(50)
account.deposit(20)
assert account.balance == 170Проблемы:
BankAccount(100) повторяется 3 разаimport pytest
from src.bank_account import BankAccount
@pytest.fixture
def account():
"""Создаёт BankAccount с балансом 100"""
return BankAccount(100)
# ✅ ХОРОШО — используем фикстуру
def test_deposit(account):
"""Тест пополнения"""
account.deposit(50)
assert account.balance == 150
def test_withdraw(account):
"""Тест снятия"""
account.withdraw(30)
assert account.balance == 70Что делает pytest:
account в тестеaccount✅ Теперь setup в одном месте!
@pytest.fixture
def account():
print("🔧 Setup: создаём account")
return BankAccount(100)
def test_deposit(account):
print("🧪 Тест запущен")
account.deposit(50)
assert account.balance == 150Вывод:
🔧 Setup: создаём account
🧪 Тест запущен
PASSED
Фикстура вызывается ПЕРЕД каждым тестом!
@pytest.fixture
def empty_account():
"""Пустой счёт"""
return BankAccount(0)
@pytest.fixture
def rich_account():
"""Счёт с большим балансом"""
return BankAccount(10000)
def test_transfer(empty_account, rich_account):
"""Перевод денег между счетами"""
amount = 500
rich_account.withdraw(amount)
empty_account.deposit(amount)
assert rich_account.balance == 9500
assert empty_account.balance == 500@pytest.fixture
def user():
"""Создаёт пользователя"""
return User("Alice", 25)
@pytest.fixture
def user_with_account(user):
"""Создаёт пользователя СО счётом"""
account = BankAccount(1000)
user.account = account
return user
def test_user_can_deposit(user_with_account):
"""Пользователь может пополнить счёт"""
user_with_account.account.deposit(500)
assert user_with_account.account.balance == 1500@pytest.fixture # scope="function" по умолчанию
def account():
print("🔧 Создаём account")
return BankAccount(100)
def test_deposit(account):
pass
def test_withdraw(account):
passРезультат: каждый тест получает НОВЫЙ объект.
@pytest.fixture(scope="module")
def database_connection():
"""Подключение к БД (медленно!)"""
connection = connect_to_db()
return connection
def test_insert_user(database_connection):
pass
def test_select_user(database_connection):
pass✅ Подключение создано ОДИН раз для всего модуля!
@pytest.fixture(scope="function") # Каждый тест (default)
@pytest.fixture(scope="class") # Один раз для класса
@pytest.fixture(scope="module") # Один раз для файла
@pytest.fixture(scope="session") # Один раз для всей сессииКогда использовать:
function — быстрые объекты (User, BankAccount) ✅ Defaultmodule — медленные объекты (DB connection, API client)session — очень медленные (Docker container, test database)@pytest.fixture
def temp_file():
"""Создаёт временный файл"""
file = open("test.txt", "w")
file.write("test data")
file.close()
return "test.txt"
# ❌ Файл останется после теста!@pytest.fixture
def temp_file():
"""Создаёт временный файл и удаляет после теста"""
# Setup
filename = "test.txt"
with open(filename, "w") as f:
f.write("test data")
yield filename # Возвращаем в тест
# Teardown (выполнится ПОСЛЕ теста)
import os
os.remove(filename)
def test_read_file(temp_file):
"""Тест чтения файла"""
with open(temp_file, "r") as f:
content = f.read()
assert content == "test data"
# После этого теста файл удалится!Порядок выполнения:
yield filename → передаём в тест# tests/conftest.py (фикстуры доступны во ВСЕХ тестах)
import pytest
from src.bank_account import BankAccount
from src.user import User
@pytest.fixture
def empty_account():
"""Пустой банковский счёт"""
return BankAccount(0)
@pytest.fixture
def account():
"""Счёт с начальным балансом"""
return BankAccount(100)
@pytest.fixture
def user():
"""Пользователь"""
return User("Alice", 25)
@pytest.fixture
def user_with_account(user, account):
"""Пользователь со счётом"""
user.account = account
return user
@pytest.fixture(scope="module")
def database():
"""Подключение к БД (медленно)"""
db = connect_to_test_db()
yield db
db.close()# tests/test_bank_account.py
@pytest.fixture
def account():
return BankAccount(100)
def test_deposit(account):
passКогда: фикстура используется ТОЛЬКО в этом файле.
# tests/conftest.py
@pytest.fixture
def account():
return BankAccount(100)Когда: фикстура используется в НЕСКОЛЬКИХ файлах.
✅ Pytest автоматически находит conftest.py и загружает фикстуры!
@pytest.fixture — переиспользование setup-кодаyield — cleanup после тестовconftest.py — глобальные фикстурыВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.
Далее: Отладка: понимаем ошибки