Источник: Nuances of Programming
Вы только что написали фрагмент кода и не знаете, что делать дальше. Отправлять ли pull request, чтобы коллеги провели ревизию кода или же протестировать его вручную?
Конечно же, следует сделать и то, и другое, но с небольшой оговоркой: сначала нужно провести модульное тестирование и убедиться, что код работает, как надо.
Модульные тесты можно пройти или не пройти, и это превращает их в отличный метод для проверки кода. В данной статье я покажу, как писать модульные тесты на Python, и вы увидите, как просто внедрять их в свои проекты.
Начинаем
Легче всего разобраться с тестированием на практических примерах. Поэтому я напишу простую функцию в файле name_function.py. Она берет имя и фамилию и возвращает полное имя:
Функция formatted_name() берет имя и фамилию и соединяет их пробелом между значениями. Таким образом, генерируется полное имя. Затем первая буква каждого слова заменяется на заглавную. Для проверки работоспособности напишите код, который будет использовать данную функцию. В names.py я напишу простой код, который позволит пользователям вводить свои имя и фамилию:
Такой код импортирует formatted_name() из name_function.py и при запуске позволяет пользователям вводить последовательности имен и фамилий. Затем он выдает отформатированные полные имена.
Модульные тесты и сценарии
В стандартной библиотеке Python есть модуль под названием unittest. В нем содержатся инструменты для тестирования кода. Модульные тесты проверяют, что все отдельные части функции работают корректно. Это значительно упрощает их дальнейшую интеграцию.
Тестовый сценарий — это набор модульных тестов, подтверждающих корректность работы функции во всех возможных ситуациях. Тестовый сценарий должен учитывать любые входные значения, которые функция может получить от пользователей. Поэтому необходимо проводить тесты, моделирующие каждую из подобных ситуаций.
Прохождение теста
Вот стандартный сценарий для написания тестов.
Cоздайте тестовый файл. Затем импортируйте модуль unittest, определите тестовый класс, который наследуется из unittest.TestCase, и, наконец, напишите цепочку методов для тестирования всех сценариев поведения функции.
Ниже приведено подробное объяснение каждой строки кода:
Для начала импортируйте unittest и функцию для тестирования — formatted_name(). Затем создайте класс (например, NamesTestCase) с тестами для вашей функции formatted_name(). Этот класс наследуется из класса unittest.TestCase.
В NamesTestCase содержится один метод для проверки одной части formatted_name(). Этот метод можно назвать test_first_last_name().
Помните, что при запуске test_name_function.py будут автоматически запускаться все методы, начинающиеся с test_.
Внутри тестового метода test_first_last_name() вам нужно будет вызвать функцию для тестирования и сохранить возвращаемое значение. В нашем примере мы будем вызывать formatted_name() с аргументами pete и seeger, а результат сохраним в результирующей переменной.
В последней строке воспользуемся методом assert. Он проверяет, что полученный результат соответствует ожидаемому. Мы знаем, что функция formatted_name() будет возвращать полное имя с первыми заглавными буквами, поэтому ожидаемым результатом является Pete Seeger. Воспользуемся методом assertEqual() из unittest для проверки.
self.assertEqual(result, “Pete Seeger”)
По сути, эта строка означает следующее: сравнить значение результирующей переменной с Pete Seeger. Если значения соответствуют, — ОК, тест пройден. Если нет, то выдаем предупреждение.
Запуская test_name_function.py, вы ожидаете получить ОК, то есть подтверждение того, что тест был пройден успешно.
Падение теста
Чтобы показать вам, как выглядит падение теста, я изменю функцию formatted_name() и добавлю туда аргумент с отчеством. Перепишем функцию следующим образом:
Такая версия formatted_name() работает для пользователей, указавших отчество. Но при тестировании вы увидите, что для людей, не заполнивших это поле, функция выдаст ошибку.
И теперь при запуске функции test_name_function.py результат будет выглядеть вот так:
В этом сообщении вы видите информацию о месте непрохождения теста:
- Первый элемент вывода — Error. Он говорит о том, что как минимум один тест в сценарии был выполнен с ошибкой.
- Далее вам указывается файл и метод с ошибкой.
- Затем строка с ошибкой.
- И тип ошибки. В нашем случае — это отсутствие аргумента middle_name.
- Вы также увидите количество выполненных тестов, время выполнения и текстовое сообщение о статусе тестирования с количеством ошибок.
Что делать при падении теста?
Прохождение теста означает, что функция работает, как нужно. Падение теста предвещает вам много «веселья».
Я встречал нескольких программистов, которые предпочитали менять сами тесты, а не дорабатывать код. Не делайте так! Потратьте время на исправление ошибки, ведь так вы лучше поймете свой код и хорошо сэкономите время в дальнейшем.
Изначально для нашей функции formatted_name() требовалось два параметра, а затем, после перезаписи, ей понадобился еще один: отчество. Добавление отчества «сломало» функцию, нарушив ее поведение. И раз уж мы решили исправлять код, а не корректировать тесты, то правильнее будет добавить необязательное поле с отчеством.
После этого нужно убедиться в прохождении тестов в обоих случаях: при указании только имени и фамилии (например, Pete Seeger), и при указании полного имени с отчеством (например, Raymond Red Reddington). Давайте еще раз изменим код formatted_name():
Теперь функция работает для имен с отчеством и без него. Еще раз убедимся, что при прохождении теста мы опять получим Pete Seeger:
Это я и хотел вам показать. Лучше подгонять код под тестирование, а не наоборот. Теперь пора добавить новый тест для имен с отчеством.
Добавление новых тестов
Напишите новый метод в классе NamesTestCase. Он будет тестировать отчества:
Теперь оба теста пройдены успешно:
Отличная работа!
Мы написали тесты для проверки работоспособности функции с указанным или отсутствующим отчеством.
Читайте также:
Читайте нас в телеграмме и vk
Перевод статьи Goran Aviani: An Introduction to Unit Testing in Python