


pytest.mark.parametrize запускает один тест несколько раз с разными аргументами. Это удобно, когда правило проверки одинаковое, а входные данные должны покрыть разные границы. Каждый набор становится отдельным тестовым случаем с собственным результатом.
В примере Mentoring.digital проверим число страниц в выдаче. Задача небольшая, поэтому видно, что именно защищает каждый случай и почему одного «обычного» значения недостаточно.
Пусть функция получает два целых числа: количество элементов total и положительный размер страницы page_size. Для пустой выдачи нужно вернуть 0, для неполной страницы округлить число страниц вверх. Отрицательное количество элементов и неположительный размер страницы считаем ошибкой. Проверка типов в этот учебный контракт не входит.
Создайте файл pagination.py:
def page_count(total, page_size):
if total < 0 or page_size <= 0:
raise ValueError("invalid pagination")
return (total + page_size - 1) // page_sizeПолезно заранее выписать классы входов: пустая выдача, один элемент, полная страница, первый элемент следующей страницы и несколько полных страниц. Здесь важна разница поведения, а не количество случайных чисел.
Установите pytest в отдельное виртуальное окружение проекта. В командах ниже python должен указывать на его интерпретатор:
python -m pip install pytest
python -m pytest --versionРядом с модулем создайте test_pagination.py. Первый аргумент декоратора перечисляет имена параметров. Каждая строка данных содержит значения в том же порядке:
import pytest
from pagination import page_count
@pytest.mark.parametrize(
"total,size,expected",
[
pytest.param(0, 20, 0, id="empty"),
pytest.param(1, 20, 1, id="one-item"),
pytest.param(20, 20, 1, id="exact-page"),
pytest.param(21, 20, 2, id="partial-page"),
pytest.param(40, 20, 2, id="two-pages"),
],
)
def test_page_count(total, size, expected):
assert page_count(total, size) == expectedУ всех пяти случаев одна проверка: фактическое число страниц равно ожидаемому. Добавление данных не меняет тело теста. Если для нового сценария приходится писать условие внутри теста и выбирать совсем другой способ проверки, возможно, ему нужна отдельная функция.
Имена empty, exact-page и partial-page описывают назначение случаев. По имени упавшего теста будет понятно, какая граница нарушена. Идентификатор не влияет на логику проверки.
python -m pytest --collect-only -q
python -m pytest -q -k partial-pageПервая команда показывает собранные случаи, вторая выбирает тесты, чьё имя соответствует выражению -k. Перед отладкой большой матрицы стоит посмотреть сборку: случайно созданные комбинации заметны ещё до запуска.
Не складывайте в ID токены доступа, персональные данные и большие исходные документы. Имена тестов попадают в логи. Короткое название сценария полезнее полного представления сложного объекта.
Неверные входы относятся к другому ожидаемому поведению. Добавьте в тот же тестовый файл ещё одну параметризованную функцию:
@pytest.mark.parametrize(
"total,size", [(-1, 20), (1, 0), (1, -5)],
ids=["negative-total", "zero-size", "negative-size"],
)
def test_invalid_pagination(total, size):
with pytest.raises(ValueError, match="invalid"):
page_count(total, size)pytest.raises требует указанное исключение внутри своего блока. Выражение match проверяет текст ошибки как регулярное выражение. Если ошибка не возникла или имеет другой тип, случай не пройдёт.
Не объединяйте успешные и ошибочные сценарии только ради одной функции. Отдельные тесты здесь проще читать: один подтверждает результат, другой подтверждает отказ от недопустимого входа.
python -m pytest -qВ локальном окружении Python 3.12.10 и pytest 9.1.1 этот набор дал восемь успешных случаев: пять расчётов и три исключения. Затем мы намеренно заменили формулу на total // page_size. Упали случаи one-item и partial-page; остальные шесть прошли. После восстановления формулы снова прошли все восемь.
Это полезная проверка смысла теста. Зелёный запуск показывает согласованность текущего кода и ожиданий, а контролируемый дефект показывает, обнаруживает ли набор интересующую ошибку. Такой эксперимент делайте только в своей учебной копии или рабочей ветке с понятным восстановлением.
Если трудно выбрать границы, на занятии с ментором по автотестированию можно разобрать один реальный модуль и составить набор проверок по его контракту. А при затруднениях с исключениями и устройством функций поможет наставник по Python.