CLI вернул правильный код, но сломал вывод: проверяем capsys
Команду используют и люди, и соседний скрипт. Успешный результат читается из stdout, ошибка должна попасть в stderr, а статус завершения показывает успех или отказ. Если проверить только возвращаемое число, можно пропустить сообщение об ошибке в потоке данных. Следующая программа попробует разобрать его как полезный результат.
У pytest есть capsys для захвата записи через Python-потоки stdout и stderr. Вызов readouterr возвращает накопленные строки и очищает захваченный буфер для следующего чтения. Это позволяет проверить оба канала после вызова функции команды, не запуская отдельную оболочку для каждого небольшого сценария.
Два потока вывода
Сделаем команду, которая выводит количество переданных задач. Пустой список считаем ошибкой вызова. Функция возвращает код, а внешний main при запуске процесса передаёт его в SystemExit. Здесь тестируем контракт вывода и статуса непосредственно, чтобы ошибка парсера аргументов не отвлекала от нужного поведения.
import sys
def count_tasks(tasks: list[str]) -> int:
if not tasks:
print("no tasks", file=sys.stderr)
return 2
print(len(tasks))
return 0
def test_success(capsys):
assert count_tasks(["a", "b"]) == 0
captured = capsys.readouterr()
assert captured.out == "2\n"
assert captured.err == ""
def test_failure(capsys):
assert count_tasks([]) == 2
captured = capsys.readouterr()
assert captured.out == ""
assert captured.err == "no tasks\n"
Контракт сообщения и статуса
Сохраните как test_cli.py, установите pytest командой python -m pip install pytest и выполните python -m pytest -q test_cli.py. Оба теста должны пройти. Если убрать file=sys.stderr, отрицательный тест упадёт: ошибка появится в out, а err окажется пустым. Если оставить нужный канал, но забыть ненулевой статус, проверка кода также заметит дефект.
Проверяйте только те детали оформления, которые действительно входят в интерфейс. Для машинного вывода точная строка и завершающий перевод строки часто важны. Для длинного диагностического сообщения можно проверять устойчивую причину и нужный идентификатор, чтобы безобидная редактура не ломала десятки тестов. Нельзя ослаблять проверку настолько, чтобы в ней осталось любое непустое сообщение.
capsys перехватывает Python-запись в потоки. Если тестируемый компонент пишет напрямую в файловые дескрипторы, нужен capfd. Если запускаете отдельный процесс, его вывод обычно удобнее получить через subprocess.run с capture_output. Выбирайте способ по границе, которую проверяет тест, а не по знакомому названию fixture.
Хороший набор проверок команды соединяет несколько уровней: функции обработки проверяют данные, тест точки входа — аргументы и статус, один процессный тест — настоящий запуск установленной команды. Не нужно весь набор делать процессным. Но и отсутствие вывода в stderr на успешном пути стоит проверять явно: случайная отладочная печать может долго оставаться незамеченной при одном assert result == 0.
Следующий шаг — перенести этот приём из отдельного файла в поддерживаемое приложение. В курсе «Python для продвинутых: ООП, типизация и тестирование» структура пакетов, объектная модель, mypy и pytest соединяются в типизированном трекере задач с хранением данных и логированием. Можно отработать связь между интерфейсом, реализацией и регрессионным тестом, а затем применить её к своему проекту.
Посмотреть программу и практику курса.
Документация: официальный источник по теме.
Другие задачи по теме: Типы, тесты и поведение программы.
Продолжить на практике
- Python для продвинутых: ООП, типизация и тестирование ↗
Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.
