Урок по треку Автоматизация тестирования 8 мин

pytest и фикстуры за 8 минут

Фикстуры и их скоупы, conftest, параметризация, маркеры, плагины.

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?
  • Как параметризовать тест и фикстуру?
  • Как запустить только смоук и как распараллелить прогон?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.