← назад к разделу

Код, который считает и печатает, — ещё не тест. Тестом он становится, когда сам решает, сошлось или нет, и громко сообщает, если не сошлось. За это отвечает одно слово — assert.

assert код == 200 True — тест идёт дальше False — остановка, тест красный ожидали 200, получили 404 print так не умеет: он печатает и идёт дальше — тест остаётся зелёным

assert пропускает дальше только истину, а при лжи останавливает и печатает сообщение — в нём и должно быть фактическое значение.

assert: утверждение, которое обязано быть истинным

живой пример

код = 200            # из прошлого примера
assert код == 200
print("проверка прошла — значит сошлось")
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →

Читается как «утверждаю: код равен 200». Если это правда, строка не делает ничего и выполнение идёт дальше. Если неправда — программа останавливается с AssertionError, и тест считается упавшим.

Сравните с ручным вариантом:

живой пример

код = 404
if код != 200:
    print("не сошлось")      # напечатали и пошли дальше — тест «прошёл»
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →

Разница принципиальная: print ничего не ломает, и такой «тест» зелёный при любой поломке. assert останавливает и помечает падение.

Сообщение — половина пользы

assert без пояснения при падении скажет немного:

AssertionError

Поэтому после условия через запятую пишут сообщение:

живой пример

# ошибка: пример падает намеренно — так выглядит проверка с сообщением
код = 404
assert код == 200, f"ожидали 200, получили {код}"
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →

AssertionError: ожидали 200, получили 404

Правило простое: в сообщении должно быть фактическое значение. «Не сошлось» не помогает; «ожидали 200, получили 404» закрывает вопрос без отладки.

Что стоит писать в сообщении:

  • что проверяли (коротко);
  • что ожидали;
  • что пришло на самом деле;
  • если ответ большой — идентификатор, по которому его найдут (номер заказа, трассировку).

Одна проверка — одно утверждение

живой пример

ответ = {"status": "PAID", "total": 4990}   # из прошлого примера
assert ответ["status"] == "PAID"
assert ответ["total"] == 4990
print("оба поля сошлись")
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →

лучше, чем

живой пример

ответ = {"status": "PAID", "total": 4990}
assert ответ["status"] == "PAID" and ответ["total"] == 4990
print("сошлось, но какая из двух проверок упала бы — неизвестно")
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →

В первом случае при падении сразу видно, какая именно часть не сошлась. Во втором — только что «что-то из двух».

Ошибки, которые не про проверку

Кроме AssertionError бывают ошибки самого кода — и это другое. Тест упал с KeyError: 'total' — значит, вы обратились к полю, которого нет. Это может быть дефект (поле обязано быть), а может быть плохой тест (поле необязательное). Различать — ваша работа.

Самые частые:

KeyError: 'total'        — в словаре нет такого ключа
IndexError               — в списке нет элемента с таким номером
TypeError                — сложили строку с числом
NameError                — обратились к имени, которого нет (опечатка)
AttributeError           — вызвали метод, которого у объекта нет
ZeroDivisionError        — деление на ноль

Читайте сообщение снизу вверх: последняя строка — что случилось, предпоследние — где именно.

try / except: поймать ошибку и не упасть

Иногда падение — ожидаемое поведение, и его надо обработать:

живой пример

строка = "4990"
try:
    сумма = int(строка)
except ValueError:
    сумма = 0
print(сумма)
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →

try — «попробуй сделать», except — «если случилась такая ошибка, сделай вот это». Ловить нужно конкретный вид ошибки, а не всё подряд:

try:
    ...
except Exception:        # так не надо
    pass

Такой код проглатывает любую поломку, включая опечатку в имени переменной, — и тест снова становится зелёным при сломанной системе. pass внутри except — почти всегда признак, что об ошибке просто не хотят думать.

В тестах try/except нужен реже, чем кажется. Обычный тест должен падать — это его работа. Ловят ошибку там, где проверяют, что она случилась, или где нужно прибраться за собой.

Проверить, что ошибка случилась

живой пример

try:
    int("не число")
    assert False, "ожидали ValueError, но его не было"
except ValueError:
    pass     # всё правильно: ошибка и должна была случиться
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →

В pytest для этого есть более короткая запись (pytest.raises), но механика та же, и понимать её полезно.

Что решать

Коротко

  • assert условие, "сообщение" — то, что превращает код в тест: при неправде останавливает и помечает падение.
  • В сообщении обязательно фактическое значение: «ожидали 200, получили 404».
  • Одна проверка — одно утверждение: иначе непонятно, что именно сломалось.
  • KeyError, IndexError, TypeError — ошибки кода, а не проверки; читайте сообщение снизу вверх.
  • try/except ловит конкретный вид ошибки; except Exception: pass делает тест зелёным при сломанной системе.

Что почитать дальше