pytest стандарт де-факто для автотестов на Python: и для API, и для UI через Selenium или Playwright. На собесе почти всегда спрашивают фикстуры, их скоупы и параметризацию.
Тест это функция
def test_discount_applied():
cart = Cart()
cart.add(price=1000)
cart.apply_promo("SALE10")
assert cart.total == 900
pytest находит файлы test_*.py или *_test.py, функции test_* и методы test_* в классах Test*. Обычный assert переписывается при импорте, поэтому при падении видно, чему были равны обе стороны.
Фикстуры
Фикстура готовит окружение и отдаёт его тесту через аргумент:
import pytest
@pytest.fixture
def user(api):
u = api.create_user(role="customer")
yield u # тест работает здесь
api.delete_user(u.id) # teardown выполнится даже при падении теста
Фикстуры могут зависеть от других фикстур (user зависит от api). pytest сам строит граф и вызывает их в нужном порядке, а teardown идёт в обратном.
Скоупы
| scope | Сколько раз создаётся |
|---|---|
function (по умолчанию) | на каждый тест |
class | на класс тестов |
module | на файл |
package | на пакет |
session | один раз на весь прогон |
Дорогие вещи (браузер, клиент API, подключение к базе) делают session, изменяемые данные function. Фикстура широкого скоупа не может зависеть от фикстуры узкого: pytest бросит ScopeMismatch.
Фикстуры более широкого скоупа создаются раньше. autouse=True подключает фикстуру ко всем тестам в зоне видимости без явного запроса.
conftest.py
Фикстуры из conftest.py видны всем тестам в этой папке и ниже без импорта. Корневой conftest обычно держит клиенты и конфиг, вложенные дают фикстуры конкретной области.
Параметризация
@pytest.mark.parametrize("email, valid", [
("a@b.ru", True),
("no-at-sign", False),
("", False),
], ids=["ok", "no-at", "empty"])
def test_email_validation(email, valid):
assert is_valid_email(email) is valid
Одна функция, три теста в отчёте. Параметризовать можно и фикстуру: @pytest.fixture(params=["chromium", "firefox"]) и request.param внутри.
Маркеры и отбор
@pytest.mark.smokeи запускpytest -m "smoke and not slow".-k "login and not admin"отбирает по именам тестов.- Свои маркеры регистрируют в
pytest.ini(markers = ...), а--strict-markersпревращает опечатку в ошибку. skip,skipif(условие, reason=...),xfailдля известного бага.
Встроенные фикстуры
tmp_path (временная папка), monkeypatch (подмена атрибутов и переменных окружения), capsys (перехват вывода), caplog (логи), request (информация о текущем тесте).
Плагины, которые спрашивают
pytest-xdist: параллельный запуск,pytest -n auto.pytest-rerunfailures:--reruns 2для перезапуска упавших.allure-pytest: отчёт Allure,--alluredir=allure-results.pytest-playwright: фикстурыpage,browser.
Полезные флаги: -x (остановиться на первом падении), --lf (только упавшие в прошлый раз), -v, -s (не перехватывать вывод).
Частые ошибки
- Teardown после
yieldбез учёта того, что setup мог упасть на середине. - Session-фикстура, которая отдаёт изменяемый объект, и тесты портят его друг другу.
- Логика теста в фикстуре, а в самом тесте только
assert True. - Параметризация без
ids: в отчёте непонятные имена.
Что спросят на собесе
- Что такое фикстура и чем
yieldлучшеreturn? - Какие бывают скоупы и что будет, если session-фикстура зависит от function-фикстуры?
- Зачем нужен conftest.py?
- Как параметризовать тест и фикстуру?
- Как запустить только смоук и как распараллелить прогон?