Зачем это спрашивают
Фикстуры — основа любого проекта автотестов на pytest. Через них готовят данные, создают клиентов API, открывают браузер и убирают за собой. Интервьюер спрашивает про них, чтобы понять, писали ли вы тесты сами или только запускали чужие. По ответу видно, знаете ли вы, где держать общую подготовку, как не оставлять мусор на стенде и почему тесты не должны зависеть друг от друга.
Обычно за первым вопросом сразу идут уточнения: как сделать уборку после теста и что такое scope. Готовьтесь отвечать на все три части сразу.
Как построить сильный ответ
Определение
Фикстура — функция с декоратором @pytest.fixture, которая готовит что-то для теста: данные, объект, соединение. Тест получает результат фикстуры, если указывает её имя в параметрах. pytest сам находит фикстуру, вызывает её до теста и передаёт значение.
Подготовка и уборка через yield
Если после теста нужно что-то убрать, фикстура пишется через yield. Код до yield — подготовка, значение после yield получает тест, код после — уборка. Уборка выполняется после теста, даже если тест упал на assert. Так созданные данные не копятся на стенде.
import pytest
import requests
BASE_URL = "http://localhost:8000"
@pytest.fixture
def api():
session = requests.Session()
yield session
session.close()
@pytest.fixture
def note(api):
resp = api.post(f"{BASE_URL}/api/notes", json={"text": "черновик"})
resp.raise_for_status()
created = resp.json()
yield created
api.delete(f"{BASE_URL}/api/notes/{created['id']}")
def test_note_is_draft(note):
assert note["status"] == "draft"
Пример условный: сервис заметок с методами создания и удаления. Здесь видно ещё одно свойство: фикстура note сама зависит от фикстуры api. Фикстуры складываются в цепочки, и тест запрашивает только то, что ему нужно.
conftest.py
Общие фикстуры кладут в файл conftest.py. pytest подхватывает его автоматически, импортировать ничего не нужно. Фикстуры из conftest.py видны всем тестам в этой папке и вложенных.
Scope
Scope задаёт, как часто фикстура создаётся заново:
| scope | когда создаётся |
|---|---|
function |
для каждого теста, по умолчанию |
class |
один раз на класс с тестами |
module |
один раз на файл |
package |
один раз на пакет |
session |
один раз на весь прогон |
session подходит для дорогого и неизменяемого: адрес стенда, конфиг, авторизованная сессия, запущенный браузер. Данные, которые тесты меняют, лучше создавать на каждый тест. Если два теста делят один заказ и первый меняет его статус, результат второго зависит от порядка запуска — это прямой путь к нестабильным тестам.
Что ещё можно упомянуть
autouse=True— фикстура применяется ко всем тестам в области видимости без явного запроса. Удобно, но прячет зависимости, пользоваться аккуратно.- Параметризованная фикстура (
params=[...]) запускает зависящие от неё тесты по разу для каждого значения. - Встроенные фикстуры:
tmp_pathдля временных файлов,monkeypatchдля подмены переменных окружения и атрибутов. - Альтернатива
yield—request.addfinalizer, ноyieldчитается проще.
Пример ответа
Фикстура в pytest — это функция с декоратором pytest.fixture, которая готовит что-то для теста: тестовые данные, клиента API, браузер. Тест получает её, просто указав имя в параметрах. Если нужна уборка, я пишу фикстуру через yield: до yield создаю, например, заказ через API, отдаю его тесту, а после yield удаляю. Код после yield выполнится, даже если тест упал. Общие фикстуры держу в conftest.py, pytest находит его сам. У фикстуры есть scope: по умолчанию function, то есть заново на каждый тест, ещё бывают class, module, package и session. В session я выношу дорогое и неизменяемое — конфиг стенда, авторизацию. А данные, которые тест меняет, создаю на каждый тест, чтобы тесты не зависели друг от друга и от порядка запуска.
Частые ошибки
- Уборка в конце тела теста. Если assert упадёт раньше, удаление не выполнится, и на стенде останется мусор.
- Не знать про scope или считать, что фикстура по умолчанию создаётся один раз на прогон.
- Делать изменяемые данные session-фикстурой «для скорости». Тесты начинают зависеть друг от друга и падают при смене порядка или параллельном запуске.
- Импортировать фикстуры из conftest.py вручную. Это не нужно и запутывает проект.
- Путать фикстуру с параметризацией тестов.
@pytest.mark.parametrizeперебирает входные данные, фикстура готовит окружение.
Пример на MarketCove
Допустим, вы пишете API-тесты для учебного магазина https://shop.tomiddle.com, Swagger лежит на /api/docs. Разумный набор фикстур мог бы выглядеть так:
base_urlсо scopesession— адрес магазина берётся один раз из переменной окружения;user_tokenсо scopesessionилиmodule— регистрация тестового пользователя через/api/shop/auth/*и получение токена; сам аккаунт тесты не меняют;auth_client— сессияrequestsс заголовком авторизации;orderсо scopefunction— свежий заказ через/api/shop/ordersдля каждого теста, который проверяет статусы или историю в/api/orders.
Поскольку заказы и аккаунты в магазине хранятся в памяти сервера, при перезапуске сервера данные пропадут. Поэтому фикстура не должна полагаться на заранее созданные заказы — каждый тест создаёт свои данные сам.
Задание для практики
Напишите conftest.py с тремя фикстурами: base_url (session), api (сессия requests, закрывается после использования) и product (берёт первый товар из /api/products). Затем напишите два теста, которые используют product, и запустите их с флагом --setup-show: pytest покажет, когда каждая фикстура создаётся и уничтожается. Поменяйте scope у product на module и посмотрите, как изменился вывод. Потом проверьте себя по вопросам в разделе про основы программирования.
