Код, который считает и печатает, — ещё не тест. Тестом он становится, когда сам
решает, сошлось или нет, и громко сообщает, если не сошлось. За это отвечает
одно слово — assert.
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оставляет зелёный результат на сломанном коде.- Трассировку читают снизу вверх: последняя строка что, строка над ней где, выше кто вызвал; в репорт кладут последнюю строку и первую строку кода проекта.
Что почитать дальше
- Сравнения и условия — из чего состоит само условие в
assert. - Разбор данных — где эти проверки применяют.
- pytest: первый настоящий тест — форма, в которой проверки пишут в проектах.