pytest: тестирование без ручного assert-фреймворка
core написать и запустить набор тестов pytest для функции, включая простую фикстуру или параметризацию
В C дисциплину тестирования вы получали через фреймворки типа CUnit/Check/Unity: писали main(), который регистрирует тесты, вручную формулировали макросы вида ASSERT_EQUAL, сами настраивали компиляцию тестового бинарника отдельно от основного. Это работающая, но тяжёлая инфраструктура — почти столько же кода на обвязку, сколько на сами проверки.
pytest убирает этот слой почти полностью. Тест — это обычная функция с именем test_*, обычный assert, без специальных макросов и без ручной регистрации. pytest перехватывает AssertionError от встроенного assert и сам генерирует подробный отчёт — что было слева, что справа, — не требуя от вас assert_equal(a, b) с раздельными аргументами, как в C-фреймворках.
def add(a, b):
return a + b
def test_add_positive():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, -1) == -2
Запуск: pytest в корне проекта находит все test_*.py и все функции test_* внутри без единой строки регистрации — сравните это с ручным заполнением таблицы указателей на тестовые функции в C.
Повторяющийся setup/teardown из C-тестов (открыть файл/соединение перед тестом, закрыть после — часто вручную дублируемый в каждом тесте) в pytest заменяется фикстурами (fixtures): функцией с декоратором @pytest.fixture, которую тест запрашивает как параметр. Фикстура может делать yield, разделяя код на «до» и «после» — тот же паттерн, что и with для контекстных менеджеров, только применённый к тестовой инфраструктуре.
import pytest
@pytest.fixture
def tmp_config():
cfg = {"debug": True}
yield cfg # тест получит именно это значение
cfg.clear() # выполнится после теста
def test_config_debug(tmp_config):
assert tmp_config["debug"] is True
Второй паттерн, который в C приходится писать циклом с ручным перебором тестовых кейсов внутри одного main, — это параметризация. @pytest.mark.parametrize превращает одну функцию в набор независимых тестов, каждый со своим отчётом об успехе/провале, без общего цикла, где один упавший assert останавливает проверку остальных случаев.
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 5),
(-1, -1, -2),
(0, 0, 0),
])
def test_add_parametrized(a, b, expected):
assert add(a, b) == expected
Частая ошибка C-программиста здесь — тащить привычку «один main, который последовательно печатает PASS/FAIL и возвращает код ошибки процесса». В Python вам не нужен ни main, ни ручной подсчёт: pytest сам агрегирует результаты, сам определяет код возврата процесса, и вы не пишете инфраструктуру — только содержательные проверки.
Разобранный пример
Рассмотрим функцию деления с проверкой на ноль:
def safe_divide(a, b):
if b == 0:
raise ValueError("division by zero")
return a / b
C-стиль теста был бы: вызвать функцию, вручную проверить код возврата и напечатать сообщение об ошибке. В pytest для проверки исключения используется pytest.raises как контекстный менеджер — не код ошибки, а сам факт исключения — потому что в Python исключение и есть штатный сигнал об ошибке (разобрано в модуле про исключения):
import pytest
def test_safe_divide_by_zero():
with pytest.raises(ValueError):
safe_divide(1, 0)
def test_safe_divide_normal():
assert safe_divide(10, 2) == 5
Никакой ручной проверки «если исключение не было выброшено — тест должен упасть» писать не нужно: pytest.raises сама провалит тест, если блок отработал без исключения.
Попробуй сейчас
Напишите функцию is_palindrome(s: str) -> bool. Покройте её тестами через @pytest.mark.parametrize минимум пятью случаями (пустая строка, один символ, палиндром, не палиндром, палиндром с разным регистром — решите сами, нормализовать ли регистр внутри функции). Запустите pytest -v и убедитесь, что вывод показывает каждый параметризованный случай отдельной строкой.
Ожидаемый результат: 5+ отдельных PASS/FAIL в выводе, без единого написанного вами if/print для диагностики.
Получилось, если…
Вы поняли идею, если для новой функции первым инстинктом стало написать test_*.py с обычными assert, а не собственный мини-фреймворк проверки с кодами возврата, и вы бы использовали parametrize вместо for-цикла с несколькими assert подряд в одном тесте.
Вывод
pytest — это assert без макросов, setup/teardown без дублирования (фикстуры) и таблица тестовых случаев без ручного цикла (параметризация). Вся инфраструктура регистрации и отчётности, которую в C пишете сами, здесь уже есть.
AI-generated · source-grounded review
🛡 Fact-checked: 2 risky claims verified · 2 removed · confidence: high
A second model re-checked this lesson's claims and corroborated 2 of 4 , and could not confirm 2 either way. This is incomplete corroboration, not a disagreement.
On your own course these buttons answer instantly, quizzes track what you've mastered, and lessons adapt to your gaps. Write my course