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