Тестирование кода в doctest — хороший, но не универсальный способ.
Быстро протестировать небольшой фрагмент кода и убедиться, что он работает — для этого doctest вполне подходит. Но зачастую такое тестирование неудобно: тесты загромождают код, а вынести их в отдельный файл нельзя. Это серьёзная проблема: ведь тесты зачастую объёмнее, чем тестируемые функции.Есть другой, более универсальный и гибкий метод тестирования, основанный на встроенной в Python инструкции assert (англ. «утверждение»).
Assert работает по такой логике: разработчик передаёт в него какое-то утверждение, и если оно истинно — assert не возвращает ничего, тест пройден. Если утверждение оказалось ложным — выбрасывается исключение с сообщением об ошибке, а исполнение кода прерывается.
# Синтаксис:
assert <проверяемое утверждение>, <'Сообщение об ошибке'>
# В этом примере утверждение (первый аргумент) - True.
# Код продолжит выполняться, сообщение не будет выведено
assert 1 == 1, 'Хьюстон, у нас проблемы'
# В этом примере утверждение (первый аргумент) - False
assert 2 + 2 == 5, 'Хьюстон, у нас проблемы'
# В результате будет вызвано исключение с текстом из второго аргумента,
# а выполнение кода будет остановлено
>Traceback (most recent call last):
> File "1.py", line 25, in <module>
> assert False, 'Хьюстон, у нас проблемы'
> AssertionError: Хьюстон, у нас проблемы
Тонкости форматирования
При переносе длинных строк в assert есть важный нюанс: не заключайте в скобки оба аргумента (проверяемое утверждение и сообщение об ошибке), иначе assert решит, что вы передали в него непустой кортеж, а он приводится к True
.
Протестируйте этот код в редакторе: утверждение 2 + x == 4
ложно (ведь x = 3
), но assert не вернёт сообщение об ошибке.
x = 3
# Такой перенос строк всё испортит, при любом x утверждение вернёт True:
assert (2 + x == 4, 'Очень длинная строка, в которой многословно '
'и с лирическими отступлениями описывается, '
'какой именно тест провален.')
В таком assert тесты всегда будут возвращать результат «утверждение верно», что бы ни творилось в проверяемом коде.
IDE подсвечивает ошибку переноса строки, но тем не менее нужно помнить об этой проблеме.
При переносе длинных строк в assert замыкайте в скобки только переносимую строку, а фрагмент на каждой строке обрамляйте в кавычки:
x = 3
assert 2 + x == 4, ('Очень длинная строка, в которой многословно '
'и с лирическими отступлениями описывается, '
'какой именно тест провален.')
Assert для функции
Ассерты (в отличие от доктестов) необязательно размещать в тестируемой функции. Их можно компоновать в классы или в функции, выносить в другие файлы и при необходимости импортировать и вызывать. В результате код становится чище, с ним проще работать и легче читать.Вы уже тестировали функцию movie_quotes()
в ручном режиме и через doctest. Теперь пусть поработает assert. Запустите этот код в Visual Studio Code.
def movie_quotes(name):
"""Возвращает цитаты известных персонажей из фильмов."""
quotes = {
'Элли': 'Тото , у меня такое ощущение, что мы не в Канзасе!',
'Шерлок': 'Элементарно, Ватсон!',
'Дарт Вейдер': 'Я — твой отец.',
'Thomas A. Anderson': 'Меня зовут Ханс. Ханс Кристиан Андерсен.',
}
return quotes.get(name, 'Персонаж пока не известен миллионам.')
# Утверждаем, что если в movie_quotes() передать 'Шерлок' -
# функция вернёт 'Элементарно, Ватсон!'.
assert movie_quotes('Шерлок') == 'Элементарно, Ватсон!', (
"movie_quotes('Шерлок') не вернул ожидаемый результат!")
# Утверждаем, что если в movie_quotes() передать 'Thomas A. Anderson' -
# функция вернёт 'Меня зовут Нео!'.
assert movie_quotes('Thomas A. Anderson') == 'Меня. Зовут. Нео!', (
"movie_quotes('Thomas A. Anderson') не вернул ожидаемый результат!")
# Утверждаем, что если в movie_quotes передать 'Алиса Плезенс Лидделл' -
# функция вернёт 'Всё чудесатее и чудесатее!'.
expected_answer = 'Всё чудесатее и чудесатее!'
assert movie_quotes('Алиса Плезенс Лидделл') == expected_answer, (
"movie_quotes('Алиса Плезенс Лидделл') не вернул ожидаемый результат!")
Тест провален: при вызове movie_quotes('Thomas A. Anderson')
Нео зачем-то представился именем великого сказочника.В словаре quotes
в элементе с ключом Thomas A. Anderson
измените значение: вместо 'Меня зовут Ханс. Ханс Кристиан Андерсен.'
поставьте 'Меня. Зовут. Нео!'
и запустите код ещё раз.
Снова провал: в словаре нет Алисы! Это недопустимая ошибка, надо её исправить, ведь вокруг — страна чудес. В тесте ожидалась реплика Алисы, но функция вернула сообщение, что персонажа нет в списке.
Добавьте в словарь quotes
новый элемент c ключом 'Алиса Плезенс Лидделл'
и значением 'Всё чудесатее и чудесатее!'
. Снова запустите код.
Ну, наконец-то: теперь код соответствует тестам. Можно продолжать работу.
Assert для классов
Потестируем класс, описывающий отдельную запись в контакт-листе (вы уже работали с этим классом и отлично аннотировали его):
class Contact:
def __init__(self, name, year_birth, is_programmer):
self.name = name
self.year_birth = year_birth
self.is_programmer = is_programmer
def age_define(self):
if 1946 < self.year_birth < 1980:
return 'Олдскул'
if self.year_birth >= 1980:
return 'Молодой'
return 'Старейшина'
def programmer_define(self):
if self.is_programmer:
return 'Программист'
return 'Нормальный'
def show_contact(self):
return(f'{self.name}, '
f'категория: {self.age_define()}, '
f'статус: {self.programmer_define()}')
def print_contact(self):
print(self.show_contact())
Чтобы протестировать этот класс, проще всего создать его экземпляр и убедиться, что поведение экземпляра соответствует ожиданиям.
mike: Contact = Contact('Михаил Булгаков', 1891, False)
expected_string = 'Михаил Булгаков, категория: Старейшина, статус: Нормальный'
assert mike.show_contact() == expected_string, 'Ошибка в Contact.show_contact()'
За кулисами assert
При обработке инструкций assert Python преобразует каждую из них примерно в такую конструкцию:
if __debug__:
if not <утверждение>:
raise AssertionError(<сообщение об ошибке>)
Эта инструкция работает только в том случае, когда для встроенной константы Python __debug__
установлено значение True
. При оптимизации проекта это значение меняют на False
, и в таком режиме ассерты игнорируются.
Применяйте assert только для тестирования. Практичность таких тестов в том, что их можно поставить в любом месте кода и быстро проверить какой-то фрагмент программы.
Doctest или assert — что правильно?
Правильны и полезны оба подхода, их можно применять как по отдельности, так и совместно. Выбор варианта зависит от сложности и структуры тестируемого объекта: как правило, doctest применяют для быстрого простого тестирования небольших фрагментов кода.