


Фикстура pytest готовит данные или ресурс для теста и, когда нужно, освобождает его после использования. Тест получает значение через аргумент с именем фикстуры. Самый безопасный первый выбор для изменяемых данных: отдельный экземпляр на каждый тест, то есть стандартный scope function.
На Mentoring.digital разберём два небольших примера: словарь остатков и временную базу SQLite в памяти. Они показывают, почему удобная общая подготовка не должна незаметно превращаться в общее изменяемое состояние.
Сохраните пример в test_inventory.py. В окружении проекта должен быть установлен pytest. Запустите файл командой python -m pytest -q test_inventory.py. pytest обнаружит функцию с декоратором fixture и передаст её результат в каждый тест, который запросил inventory.
import pytest
@pytest.fixture
def inventory():
return {"pencil": 3}
def test_reserve(inventory):
inventory["pencil"] -= 1
assert inventory["pencil"] == 2
def test_initial_stock(inventory):
assert inventory["pencil"] == 3
Первый тест меняет словарь. Второй всё равно получает исходное количество 3, потому что фикстура без явного scope создаётся заново для каждого теста. Это не копирование одного глобального словаря: функция действительно возвращает новый объект при каждом таком создании.
Имя аргумента важно. Если переименовать аргумент в stock, а фикстуру оставить inventory, pytest будет искать фикстуру stock и сообщит, что не нашёл её. Общие для нескольких файлов фикстуры можно размещать в conftest.py соответствующего каталога; импортировать их в каждый тестовый модуль обычно не нужно.
Если добавить scope="module" к inventory, оба теста начнут пользоваться одним словарём в модуле. При выполнении первого теста перед вторым второй увидит 2 вместо 3. Проблема находится в разделяемых данных, а не в assert.
Не расширяйте scope только ради уменьшения числа запусков. Сначала решите, что можно безопасно разделять: неизменяемую конфигурацию или дорогое соединение с явным восстановлением состояния. Параметризация и запуск в нескольких процессах добавляют свои границы: session не означает один объект на все процессы.
Широкая фикстура не может зависеть от более узкой, которая должна жить меньше неё. Например, session-фикстура, запрашивающая обычную function-фикстуру, приводит к ScopeMismatch. Обычно нужно согласовать время жизни ресурсов, а не подавлять ошибку.
Для закрываемого ресурса удобно разделить подготовку и очистку через yield. Следующий файл использует стандартный модуль sqlite3 и не подключается к рабочей базе.
import sqlite3
import pytest
@pytest.fixture
def db():
connection = sqlite3.connect(":memory:")
try:
connection.execute(
"CREATE TABLE items (name TEXT NOT NULL)"
)
yield connection
finally:
connection.close()
def test_insert(db):
db.execute("INSERT INTO items VALUES (?)", ("pencil",))
assert db.execute("SELECT COUNT(*) FROM items").fetchone()[0] == 1
def test_empty(db):
assert db.execute("SELECT COUNT(*) FROM items").fetchone()[0] == 0
Значение после yield получает тест. После его завершения pytest продолжает выполнение фикстуры и выполняет finally, в том числе при проваленном утверждении теста. Здесь каждая фикстура создаёт отдельную базу в памяти, поэтому test_empty не зависит от вставки в другом тесте.
Если создание ресурса не удалось до его присваивания, освобождать ещё нечего. Если ошибка возникла после открытия connection внутри try, finally закроет соединение. Для нескольких ресурсов безопаснее делать небольшие фикстуры с понятной ответственностью, а не длинную подготовку с десятком побочных действий.
Запустите подозрительный тест отдельно, затем вместе с соседом и в обратном порядке, передав pytest явные имена test_file.py::test_name. Разница результата часто указывает на общие коллекции, неочищенную базу, переменную окружения или глобальное состояние приложения.
Проверьте, кто создаёт объект, кто его меняет и кто возвращает исходное состояние. Не лечите зависимость переименованием файлов ради удобного порядка запуска. На курсе по тестированию такой пример помогает разобраться с подготовкой окружения, а наставнику по QA полезно показать минимальную пару тестов, которые влияют друг на друга.