unittest против pytest

Прямое сравнение unittest и pytest и обзор экосистемы pytest.

Экосистема pytest — большой набор плагинов (для покрытия, моков, асинхронности, повторов), которые подключаются одной установкой.

Сводная таблица сравнения

Критерийunittestpytest
Установкавстроен в Pythonpip install pytest
Структура тестакласс + методыфункции (можно и классы)
Проверкиметоды assert*обычный assert
ФикстурыsetUp/tearDown@pytest.fixture по аргументам
ПараметризацияsubTest@pytest.mark.parametrize
Плагинымалоогромная экосистема
Многословностьвышениже

Когда что выбирать

Это не «или-или», а вопрос контекста:

  • unittest — когда нельзя ставить зависимости (минимальное окружение, скрипт, стандартная поставка), или когда тестов немного. Он всегда «уже есть».
  • pytest — для большинства реальных проектов: меньше шаблонного кода, удобные фикстуры и параметризация, богатая экосистема.

Важно: оба инструмента опираются на одни и те же идеи — TestCase/AAA, фикстуры, параметризация, моки. Освоив unittest, вы перейдёте на pytest за вечер: меняется синтаксис, а не принципы.

Мокинг в обоих мирах

Хорошая новость: unittest.mock (Mock, patch, MagicMock) — это часть стандартной библиотеки и одинаково работает и в unittest, и в pytest. То, что вы изучили в разделе про моки, переносится без изменений. Для pytest есть и обёртка-плагин pytest-mock (фикстура mocker), но под капотом тот же unittest.mock.

# pytest-стиль с фикстурой mocker (плагин pytest-mock)
def test_roll(mocker):
    mocker.patch("random.randint", return_value=4)
    assert lucky_roll() == 4

Популярные плагины pytest

ПлагинЧто добавляет
pytest-covотчёт о покрытии кода тестами
pytest-mockудобная фикстура mocker поверх unittest.mock
pytest-xdistпараллельный запуск тестов на нескольких ядрах
pytest-asyncioтестирование async-кода
pytest-djangoинтеграция с Django

Подключаются плагины просто установкой — pytest сам их подхватывает:

pip install pytest-cov pytest-mock
# запуск с измерением покрытия:
pytest --cov=myapp

Миграция с unittest на pytest

Переход обычно делают постепенно, без переписывания всего разом. Сначала ставят pytest и просто запускают им существующие unittest-тесты — команда меняется с python -m unittest на pytest, а сами тесты остаются как есть. Затем новые тесты пишут уже в pytest-стиле (функции вместо классов, assert вместо assertEqual), а старые трогают только при изменении соответствующего кода. Так проект живёт со смешанным набором тестов, и это нормально — оба стиля прекрасно сосуществуют в одном прогоне.

Один нюанс: фикстуры setUp/tearDown работают только внутри классов-наследников TestCase. Если переписываете тест в pytest-функцию, замените их на фикстуры @pytest.fixture. А вот моки переносятся буквально без изменений — это всё тот же unittest.mock.

Главный вывод раздела

Не нужно выбирать «навсегда». Начните с unittest — он встроен и учит фундаменту: TestCase, фикстуры, assert-методы, моки. Когда проект вырастет, переходите на pytest ради краткости, удобных фикстур и богатой экосистемы плагинов; ваши прежние тесты при этом продолжат запускаться. Принципы тестирования — изоляция, детерминизм, осмысленные проверки — важнее конкретного фреймворка и одинаково применимы к обоим.

Итог

  • unittest встроен и хорош для минимального окружения; pytest удобнее для крупных проектов.
  • Идеи (AAA, фикстуры, параметризация, моки) общие — переход между ними лёгкий.
  • unittest.mock работает в обоих фреймворках одинаково.
  • Экосистема плагинов pytest (cov, mock, xdist) подключается установкой.
Проверьте себя
1. Когда разумнее выбрать unittest, а не pytest?
AВсегда
BКогда нельзя ставить зависимости (минимальное окружение) или тестов немного — он встроен
CНикогда
DТолько для async
2. Работает ли unittest.mock в pytest?
AНет, нужен другой мокер
BДа, это стандартная библиотека и работает в обоих фреймворках одинаково
CТолько через плагин
DТолько в Python 2
3. Что добавляет плагин pytest-cov?
AПараллельный запуск
BОтчёт о покрытии кода тестами
CПоддержку async
DИнтеграцию с Django
Поддержать проект