Перейти к содержимому
К программе курса
Pytest с нуля: Первые тесты за 2.5 часа
8 / 8100%

Отладка: понимаем ошибки

25 минут

Вы уже написали 50 тестов. 3 из них падают. Как быстро найти причину и исправить?

Цель: Научиться эффективно отлаживать тесты и читать сообщения об ошибках pytest.

Вы точно готовы?

Убедитесь, что умеете:

# Писать тесты с фикстурами
@pytest.fixture
def account():
    return BankAccount(100)
 
def test_deposit(account):
    account.deposit(50)
    assert account.balance == 150

Если фикстуры непонятны — вернитесь к уроку 6.

Проблема: непонятные ошибки

Минимальный вывод по умолчанию

pytest

Результат:

============================= test session starts ==============================
collected 10 items
 
tests/test_bank.py .F.F.....                                             [100%]
 
=================================== FAILURES ===================================
_______________________________ test_withdraw __________________________________
 
    def test_withdraw():
>       account.withdraw(200)
E       ValueError: Insufficient funds
 
tests/test_bank.py:25: ValueError
============================== short test summary info ==========================
FAILED tests/test_bank.py::test_withdraw
========================== 1 failed, 9 passed in 0.12s =========================

Проблемы:

  • ❌ Не видно какие именно тесты прошли
  • ❌ Мало информации об ошибке
  • ❌ Не понятно с какими данными упал тест

Решение 1: Verbose режимы

pytest -v: показать все тесты

pytest -v

Результат:

============================= test session starts ==============================
collected 10 items
 
tests/test_bank.py::test_create_account PASSED                          [ 10%]
tests/test_bank.py::test_deposit PASSED                                 [ 20%]
tests/test_bank.py::test_withdraw FAILED                                [ 30%]
tests/test_bank.py::test_multiple_deposits PASSED                       [ 40%]
tests/test_bank.py::test_negative_balance PASSED                        [ 50%]
...

✅ Теперь видно каждый тест!

pytest -vv: ещё больше деталей

pytest -vv

Результат для параметризованных тестов:

tests/test_bank.py::test_deposit[100-50-150] PASSED                     [ 20%]
tests/test_bank.py::test_deposit[0-100-100] PASSED                      [ 40%]
tests/test_bank.py::test_deposit[1000-1-1001] PASSED                    [ 60%]

✅ Показывает параметры для каждого теста!

Когда использовать

# Разработка: хочу видеть все тесты
pytest -v
 
# Отладка параметризованных тестов: какой именно кейс упал?
pytest -vv
 
# CI/CD: минимальный вывод для скорости
pytest

Решение 2: Stacktrace опции

--tb=short: короткий stacktrace

pytest --tb=short

Результат:

=================================== FAILURES ===================================
_______________________________ test_withdraw __________________________________
tests/test_bank.py:25: in test_withdraw
    account.withdraw(200)
E   ValueError: Insufficient funds

✅ Только суть ошибки (без полного stacktrace)

--tb=long: полный stacktrace

pytest --tb=long

Результат:

=================================== FAILURES ===================================
_______________________________ test_withdraw __________________________________
 
    @pytest.fixture
    def account():
>       return BankAccount(100)
 
tests/conftest.py:12:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
    def __init__(self, balance):
        if balance < 0:
>           raise ValueError("Balance cannot be negative")
E           ValueError: Balance cannot be negative
 
src/bank_account.py:8: ValueError

✅ Полный путь ошибки (где именно она произошла)

--tb=no: без stacktrace

pytest --tb=no

Результат:

tests/test_bank.py F
 
========================== short test summary info ==========================
FAILED tests/test_bank.py::test_withdraw
========================== 1 failed, 9 passed in 0.12s =========================

✅ Только статистика (для CI когда логи большие)

Stacktrace опции

--tb=auto      # Автоматический (default)
--tb=long      # Полный stacktrace
--tb=short     # Короткий stacktrace
--tb=line      # Только строка с ошибкой
--tb=native    # Python stacktrace
--tb=no        # Без stacktrace

Рекомендации:

  • --tb=short — для большинства случаев ✅
  • --tb=long — когда ошибка глубоко в коде
  • --tb=no — для CI (чтобы логи не раздувались)

Решение 3: Интерактивная отладка --pdb

Что такое pdb?

pdb (Python Debugger) — встроенный отладчик Python, который позволяет:

  • 🔍 Остановить программу в нужном месте
  • 🔬 Изучить значения переменных
  • 🎯 Выполнить код прямо во время работы программы
  • 🐛 Найти причину бага без перезапуска тестов

Зачем это нужно?

Вместо того чтобы добавлять print() и перезапускать тест 10 раз, вы:

  1. Запускаете тест один раз
  2. pytest останавливается на ошибке
  3. Вы исследуете всё что угодно прямо в момент падения

Это как поставить видео на паузу и рассмотреть кадр в деталях! 🎬

pytest --pdb: остановка на ошибке

pytest --pdb

Что происходит:

  1. Тест падает
  2. pytest останавливается ровно на месте ошибки
  3. Открывается интерактивная консоль Python (pdb)

Результат:

=================================== FAILURES ===================================
_______________________________ test_withdraw __________________________________
 
    def test_withdraw():
>       account.withdraw(200)
 
tests/test_bank.py:25
(Pdb) _

✅ Вы внутри теста! Можете проверить что угодно прямо здесь и сейчас.

Команды pdb

В интерактивной консоли:

(Pdb) account.balance
100
 
(Pdb) account
<BankAccount object at 0x...>
 
(Pdb) print(account.balance)
100
 
(Pdb) account.deposit(50)
(Pdb) account.balance
150
 
(Pdb) c  # Continue (продолжить выполнение)
(Pdb) q  # Quit (выйти из pdb)

Практический пример

def test_complex_calculation():
    """Тест сложного расчёта"""
    user = User("Alice", 25)
    account = BankAccount(1000)
 
    # Что-то пошло не так...
    result = calculate_interest(account, user.age)
    assert result == 50  # FAILED

Запускаем с --pdb:

pytest tests/test_bank.py::test_complex_calculation --pdb

В pdb:

(Pdb) result
45  # Ага, получили 45 вместо 50!
 
(Pdb) account.balance
1000
 
(Pdb) user.age
25
 
(Pdb) calculate_interest(account, 30)
60  # Для возраста 30 получается 60
 
(Pdb) calculate_interest(account, 25)
45  # Для возраста 25 получается 45 (не 50!)
 
# Нашли баг в тесте! Expected должно быть 45, не 50

pytest --pdbcls для улучшенной отладки

Используйте ipdb (IPython debugger) для подсветки:

# Установите ipdb
pip install ipdb
 
# Используйте вместо обычного pdb
pytest --pdb --pdbcls=IPython.terminal.debugger:TerminalPdb

✅ Получите автодополнение и подсветку синтаксиса!

Решение 4: Smart test execution

--lf: запустить только упавшие тесты

# Первый запуск: 10 тестов, 3 упали
pytest
 
# Повторный запуск: только 3 упавших теста
pytest --lf  # last-failed

Результат:

============================= test session starts ==============================
run-last-failure: rerun previous 3 failures
collected 10 items / 7 deselected / 3 selected
 
tests/test_bank.py::test_withdraw PASSED                                [ 33%]
tests/test_bank.py::test_negative PASSED                                [ 66%]
tests/test_bank.py::test_overdraft PASSED                               [100%]
 
===================== 3 passed, 7 deselected in 0.05s =======================

✅ Быстро проверяем что баги исправлены!

--ff: сначала упавшие, потом остальные

pytest --ff  # failed-first

Порядок запуска:

  1. Сначала 3 упавших теста
  2. Потом 7 прошедших тестов

✅ Ускоряет обнаружение новых ошибок!

-x: остановка на первой ошибке

pytest -x  # stop on first failure

Результат:

tests/test_bank.py::test_deposit PASSED                                 [ 10%]
tests/test_bank.py::test_withdraw FAILED                                [ 20%]
 
=================================== FAILURES ===================================
...
 
!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
========================== 1 failed, 1 passed in 0.05s =========================

✅ Не тратим время на остальные тесты — сразу чиним первую ошибку!

--maxfail=N: остановка после N ошибок

pytest --maxfail=3  # Остановиться после 3 ошибок

✅ Баланс между -x (слишком рано) и без флага (слишком поздно)

Комбинирование опций (практика)

Сценарий 1: Быстрая отладка одного теста

# Запускаем только упавший тест с отладкой
pytest tests/test_bank.py::test_withdraw -vv --pdb

Результат:

  • -vv — видим все детали теста
  • --pdb — останавливаемся на ошибке для исследования

Сценарий 2: Исправляем упавшие тесты

# 1. Запускаем все тесты
pytest
 
# 2. Смотрим только упавшие
pytest --lf -vv
 
# 3. Отлаживаем первый упавший
pytest --lf -x --pdb
 
# 4. После исправления — снова только упавшие
pytest --lf

Сценарий 3: CI/CD режим

# Минимальный вывод, без stacktrace, стоп на первой ошибке
pytest --tb=no -x -q

Результат:

.........F
 
========================== short test summary info ==========================
FAILED tests/test_bank.py::test_withdraw
!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 9 passed in 0.12s

Сценарий 4: Отладка параметризованных тестов

# Какой именно параметр упал?
pytest -vv --tb=short
 
# Отладка конкретного параметра
pytest "tests/test_bank.py::test_deposit[100-50-150]" --pdb

Читаем сообщения об ошибках

AssertionError: простая ошибка

=================================== FAILURES ===================================
_______________________________ test_deposit ___________________________________
 
    def test_deposit():
        account = BankAccount(100)
        account.deposit(50)
>       assert account.balance == 200
E       assert 150 == 200
E        +  where 150 = <BankAccount object at 0x...>.balance
 
tests/test_bank.py:15: AssertionError

Как читать:

  1. > — строка с ошибкой
  2. E assert 150 == 200 — что пошло не так (получили 150, ожидали 200)
  3. E + where — откуда взялось значение 150

Решение: Ожидаемое значение неверно — должно быть 150, не 200.

ValueError: исключение в коде

=================================== FAILURES ===================================
_______________________________ test_withdraw __________________________________
 
    def test_withdraw():
        account = BankAccount(100)
>       account.withdraw(200)
 
tests/test_bank.py:25:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
    def withdraw(self, amount):
        if amount > self.balance:
>           raise ValueError("Insufficient funds")
E           ValueError: Insufficient funds
 
src/bank_account.py:32: ValueError

Как читать:

  1. > — вызвали account.withdraw(200) в тесте
  2. _ _ _ — входим в метод withdraw
  3. E ValueError — код выбросил исключение

Решение: Тест пытается снять 200, но на счёте только 100 — это корректное поведение! Нужно либо изменить тест, либо добавить pytest.raises.

AttributeError: метод не найден

=================================== FAILURES ===================================
_______________________________ test_balance ___________________________________
 
    def test_balance():
        account = BankAccount(100)
>       assert account.get_balance() == 100
E       AttributeError: 'BankAccount' object has no attribute 'get_balance'
 
tests/test_bank.py:10: AttributeError

Решение:

  1. Опечатка в названии метода (get_balance вместо balance)
  2. Или метод не реализован

TypeError: неправильные аргументы

=================================== FAILURES ===================================
_______________________________ test_deposit ___________________________________
 
    def test_deposit():
        account = BankAccount(100)
>       account.deposit()
E       TypeError: deposit() missing 1 required positional argument: 'amount'
 
tests/test_bank.py:15: TypeError

Решение: Забыли передать аргумент amount.

Практический workflow отладки

Шаг 1: Запустите тесты

pytest -v

Шаг 2: Если упали — смотрим детали

pytest --lf -vv --tb=short

Шаг 3: Отлаживаем интерактивно

pytest --lf -x --pdb

В pdb:

(Pdb) # Исследуем переменные
(Pdb) print(account.balance)
(Pdb) print(result)
 
(Pdb) # Пробуем разные значения
(Pdb) account.deposit(50)
(Pdb) account.balance
 
(Pdb) q  # Выходим

Шаг 4: Исправляем код

# Исправили баг в src/bank_account.py или тесте

Шаг 5: Проверяем исправление

pytest --lf  # Запускаем только упавшие тесты

Шаг 6: Запускаем все тесты

pytest  # Убеждаемся что ничего не сломали

Что вы изучили

  • pytest -v/-vv — детальный вывод тестов
  • --tb опции — контроль stacktrace
  • --pdb — интерактивная отладка
  • --lf/--ff — умное переиспользование упавших тестов
  • -x/--maxfail — ранняя остановка
  • Чтение ошибок — AssertionError, ValueError, TypeError
  • Практический workflow — от ошибки до исправления

Поздравляю! 🎉

Вы завершили курс pytest-basics! Теперь вы умеете:

  • Писать базовые тесты (урок 0)
  • Организовывать тесты в проекте (урок 1)
  • Тестировать классы и ошибки (урок 2)
  • Писать чистые тесты с AAA-паттерном (урок 3)
  • Параметризовать тесты (урок 4)
  • Группировать тесты маркерами (урок 5)
  • Использовать фикстуры (урок 6)
  • Отлаживать тесты (урок 7)

Что дальше?

Вы прошли Level 1: Beginner курс. Теперь переходите к более продвинутым темам:

  • Level 2: pytest-junior — Mock'и, patching, fixtures advanced
  • Level 3: pytest-professional-tools — Plugins, custom markers, hooks
  • Level 4+: Async тестирование, property-based testing, performance тесты

Продолжайте практиковаться! Пишите тесты для своих проектов и применяйте изученные техники.

Устранение неисправностей

Запускайте pytest с флагом --pdb (не -pdb), например pytest --pdb

Проверьте:

  • Используйте полную форму флага: pytest --pdb
  • Для ipdb добавьте --pdbcls=IPython.terminal.debugger:TerminalPdb

Все тесты прошли в прошлый раз — сначала запустите обычный pytest, чтобы зафиксировать падения

Проверьте:

  • Сделайте полный запуск: pytest
  • Если не было падений, --lf ничего не запустит

Используйте минимальный вывод: pytest --tb=no -q

Проверьте:

  • Уберите -v/-vv, оставьте тихий режим -q
  • Сократите stacktrace флагом --tb=no или --tb=short

Сфокусируйтесь на ключевых строках и упростите вывод

Проверьте:

  • Читайте снизу вверх — последняя ошибка важнее всего
  • Ищите строку со знаком > (место, где упал тест)
  • Запустите с --tb=short для компактного трейсбека

Используйте простую отладку через print() и запуск pytest -s, чтобы увидеть вывод

Проверьте:

  • Добавьте print для ключевых переменных
  • Запускайте с pytest -s, чтобы вывод не скрывался

🎉 Поздравляем с завершением курса!

Поделитесь опытом и получите промокод на бесплатный доступ к любому premium-материалу на выбор

Бесплатный доступ к любому premium-материалу на выбор

🎓

Ваш опыт поможет другим студентам

📧

Промокод придет на email в течение 24 часов

Минимум 50 символов

0/50

Отладка: понимаем ошибки — Pytest с нуля: Первые тесты за 2.5 часа — Potapov.me