Тестирование при помощи инструкции assert в Python

Тестирование кода в 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 применяют для быстрого простого тестирования небольших фрагментов кода.



Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: