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

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

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

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

Обязательно

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

живой пример

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

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

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

Часто вместо этого печатают результат и смотрят глазами:

живой пример

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

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

print ничего не ломает: такая проверка остаётся зелёной при любой поломке. assert останавливает и помечает падение — этим тест и отличается от скрипта.

Отработать здесь: Проверка через assert

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

Голый assert при падении скажет только AssertionError — что не сошлось, придётся выяснять отдельно. Поэтому после условия через запятую пишут сообщение:

живой пример

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

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

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

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

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

живой пример

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

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

живой пример

# так надо: у каждого поля своё утверждение и своё сообщение
ответ = {"status": "PAID", "total": 4990}
assert ответ["status"] == "PAID", f"статус: {ответ['status']}"
assert ответ["total"] == 4990, f"сумма: {ответ['total']}"
print("оба поля сошлись")
Запустить

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

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

Отработать здесь: Собрать проверки в набор

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

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

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

ОшибкаЧто случилось
KeyError: 'total'в словаре нет такого ключа
IndexErrorв списке нет элемента с таким номером
TypeErrorсложили строку с числом
NameErrorобратились к имени, которого нет (опечатка)

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

Отработать здесь: Разные ошибки — разные сообщения

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

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

живой пример

# так не надо: except Exception прячет и опечатку тоже
try:
    сумма = int("4990")
    print(сум)
except Exception:
    pass
print("проверка зелёная, хотя сумму никто не напечатал")
Запустить

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

живой пример

# так надо: ловим только ту ошибку, которую ждали
строка = "не число"
try:
    сумма = int(строка)
except ValueError:
    сумма = 0
print(сумма)
Запустить

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

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

try/except нужен реже, чем кажется: проверка должна падать — это её работа. Ловят ошибку там, где проверяют, что она случилась.

Отработать здесь: Поймать ошибку

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

живой пример

try:
    int("не число")
    assert False, "ожидали ValueError, но его не было"
except ValueError:
    print("ValueError случилась — как и должна была")
Запустить

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

Поломка здесь — как раз отсутствие ошибки: если int вдруг проглотит «не число», сработает assert False.

Дополнительно: при первом чтении можно пропустить

Глубже: читаем tracebackрасширенное

Когда тест или сервис падает, Python печатает трассировку, stack trace: цепочку вызовов от самого внешнего к месту ошибки. Читают её снизу вверх: последняя строка говорит, что случилось, KeyError: 'total', строка над ней, где именно, файл и номер строки, а строки выше показывают, кто это место вызвал. Первая строка сверху это ваш тест, а в середине может быть код библиотеки, который вы не писали и чинить не будете.

живой пример

import traceback

def order_total(order):
    return order["total"]

try:
    order_total({"id": 7})
except KeyError:
    print(traceback.format_exc().strip().splitlines()[-1])  # KeyError: 'total'
Запустить

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

Тот же навык нужен в логах сервиса на Java или Go: в баг-репорт кладут не весь экран, а последнюю строку с ошибкой и ту строку трассировки, где впервые встречается код проекта, а не библиотеки. Если сообщения об ошибке нет вовсе, смотрят на строку выше и на запрос, который к ней привёл.

Коротко

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

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