В этой статье мы рассмотрим модуль Python unittest и некоторые из его распространенных вариантов использования.
Но перед этим давайте разберемся, зачем нам вообще нужен этот модуль.
Почему следует использовать модуль unittest?
Когда вы работаете с большими базами кода, разработка приложений часто делится на две фазы.
- Фаза разработки;
- Фаза тестирования.
Этап 1 — это этап разработки, на котором вы воплощаете свою основную идею в простое приложение.
Но этого недостаточно, если вы действительно хотите использовать его регулярно. Могли быть ситуации, которые вы могли пропустить, что может привести к неожиданной работе вашей программы.
Чтобы свести к минимуму такие ошибки, существует еще один этап, называемый этапом тестирования, который направлен на тестирование различных возможных сценариев для вашего приложения и проверку правильности его работы.
Часто, если у вас нет установленной структуры для этого этапа, вам может потребоваться проверить все сценарии вручную, что утомительно.
Unit тесты в Python. Тестирование кода | Базовый курс. Программирование на Python
Чтобы уменьшить хлопоты разработчика, мы можем использовать модуль Python unittest и решить именно эту проблему с помощью автоматического тестирования.
Виды тестирования
Для приложения существует два типа тестов:
- Комплексные тесты;
- Модульные тесты.
Интегрированные тесты — это тесты, которые проверяют, правильно ли работают модули приложения друг с другом.
Модульные тесты — это тесты, которые проверяют небольшие компоненты в приложении.
Хотя мы можем писать как интеграционные тесты, так и модульные тесты, интеграционные тесты во многом зависят от вашего приложения и могут объединять несколько модульных тестов.
Модуль Python unittest на примере
Этот модуль встроен в вашу установку Python 3+, поэтому нет необходимости устанавливать его с помощью pip.
Вы можете импортировать модуль, набрав:
import unittest
Методы
В этом модуле есть несколько методов, с помощью которых вы можете выполнять модульные тесты.
Наиболее распространенные из них перечислены в таблице ниже.
| Метод | Проверка утверждения |
| assertEqual (a, b) | а == б |
| assertNotEqual (a, b) | а! = Ь |
| assertTrue (x) | bool (x) истинно |
| assertFalse (x) | bool (x) ложно |
| assertIs (а, б) | а это б |
| assertIsNot (а, б) | а не б |
| assertIsNone (x) | x нет |
| assertIsNotNone (x) | x не None |
| assertIn (а, б) | а в б |
| assertNotIn (а, б) | а не в б |
| assertIsInstance (а, б) | isinstance (а, б) |
| assertNotIsInstance (a, b) | не isinstance (а, б) |
Написание модульного теста
Нам нужна программа для проведения тестов. Итак, напишем ее.
Писать Тесты в Python – проще чем Ты думаешь!
Я напишу программу, которая просто пытается проверить сумму элементов в списке.
Теперь, чтобы написать отдельный тестовый пример, нам нужно унаследовать класс unittest.TestCase , а затем переопределить его с помощью некоторых конкретных методов.
Я назову свой класс MyTestClass .
import unittest def list_sum(my_list): # Sums the elements of the list return sum(my_list) class MyTestClass(unittest.TestCase): def test_list(self): # Checks if the sum of the below list is as expected my_list = [1, 2, 3, 4, 5] self.assertEqual(list_sum(my_list), 15, «Should be 15») def test_string(self): # Checks if the string is ‘Hello from AskPython’ my_str = ‘Hi’ self.assertEqual(my_str, ‘Hello from AskPython’, «Should be ‘Hello from AskPython'») if __name__ == ‘__main__’: # Main module unittest.main()
Чтобы написать тестовый метод, мы должны добавить к имени метода префикс test_ . Итак, любой метод тестирования должен иметь вид test_xyz()
Я пишу метод test_list() который проверяет, равна ли сумма элементов в списке 15, и аналогично другой метод проверки данной строки.
Я использую метод unittest assertEqual() , который запускает unittest и проверяет, выполняется ли это утверждение.
Теперь давайте выполним этот файл с помощью Python.
Как видите, первый тест прошел, а второй — нет, так как строки не совпадают.
Запуск модульных тестов в приложении
Теперь давайте запустим модульные тесты в другой программе, поскольку вы не будете писать все свое приложение внутри файла unittest!
Напишем простую прикладную программу и проведем на ней модульные тесты.
Я буду писать программу, которая действует как очень простая база данных для хранения имен и оценок студентов.
Сохраните приведенный ниже файл как test_example.py как мы будем ссылаться на него в следующем фрагменте кода.
class MyClass: # Database of dict pairs db = dict() num_students = 0 def add_db(self, name, marks): self.db[name] = marks self.num_students += 1 def rm_db(self, name): # Removes key value pair corresponding # to student name if name in self.db: del self.db[name] else: return f’Student with Name: not in Database’ def get_marks(self, name): if name in self.db: return self.db[name] else: return f’Student with Name: not in Database’ if __name__ == ‘__main__’: my_class = MyClass() my_class.add_db(‘John’, 47) my_class.add_db(‘Mary’, 34) print(my_class.get_marks(‘John’))
Рекомендуемый метод
Обычно модули тестирования хранятся отдельно от основного приложения.
Таким образом, мы будем импортировать модуль unittest только на этапе тестирования.
Python позволяет нам это сделать, указав параметр -m MODULE_NAME . Итак, наша команда будет:
python -m unittest -v my_test.py
Тестирование функций и классов на Python
После написания функций и классов вы можете написать тесты для своего кода. Тестирование поможет проверить как работает ваша программа и быть уверенным, что у пользователей не будет проблем. Опытные программисты всегда тестируют свой код и выявляют ошибки до того, пока с ними не столкнулись другие пользователи. Тестировать код можно с помощью модуля Python unittest .
| 1. Тестирование функций |
| 2. Тестирование классов |
| 3. Разные методы assert |
1. Тестирование функций в Python
Модуль unittest из стандартной библиотеки Python представляет функциональную возможность для тестирования кода. Модульный тест проверяет правильность работы одного конкретного аспекта поведения функции. Тестовый сценарий — это совокупность модульных тестов, которые совместно доказывают, что функция ведет себя правильно во всех ситуациях, которые она должна обрабатывать. Часто достаточно написать модульные тесты для критичных аспектов поведения кода.
Для проведения теста напишем простую функцию, которая запрашивает два параметра «страна и столица» и выводит в отформатированном виде:
def get_country (country, capital):
«»»Возвращает отформатированную строку страна-столица.»»»
full_country = f»-»
return full_country.title()
Для написания тестового сценария для фунции, необходимо импортировать модуль unittest. Затем создать класс, наследующий от unittest.TestCase, и написать серию методов для тестирования различных аспектов поведения функции:
class CountryTestCase ( unittest.TestCase ):
«»»Тесты для функции get_country «»»
def test_get_country (self):
«»»Параметры вида ‘Uk-London’ работают правильно?»»»
formatted_country = get_country(‘uk’, ‘london’)
self . assertEqual (formatted_country, ‘Uk-London’)
if __name__ == ‘__main__’:
unittest.main()
После импортирования unittest, мы создаем класс CountryTestCase , который содержит серию модульных тестов для функции get_country(). Класс наследует от класса unittest.TestCase, чтобы Python знал как запускать тесты.
Класс CountryTestCase содержит всего один метод test_get_country() , который тестирует один аспект функции get_country()- правильность форматирования строки страна-столица. Любой метод, имя которого начинается с test_ , будет выполняться автоматически при запуске. В тестовом методе вызывается тестируемая функция get_country() c аргументами ‘uk’ и ‘london’ и сохраняет возвращаемое значение в переменную formatted_country.
self . assertEqual проверяет соответствует ли полученный результат функции, тому результату, который вы хотите получить. Метод assertEqual() из модуля unittest получает переменную formatted_country и строку ‘Uk-London’ и сравнивает их.
Блок if проверяет специальную переменную __name__, значение которой задается при выполнении программы. Если файл выполняется как главная программа, переменной __name__ будет присвоено значение __main__. В этом случае вызывается метод unittest.main() , который выполняет тестовый сценарий. В результате при запуске теста мы получим:
OK
Точка в начале сообщает, что один тест прошел успешно. Далее сообщается что на выполнение теста потребовалось 0.0001s. Сообщение Ок говорит о успешном прохождение теста.
В случае если результат будет не соответствовать, тому который вы хотите получить, будет выведено сведения о ошибке.
2. Тестирование классов на Python
Тестирование классов очень похоже на тестирование функции, так как приходится в классах тестировать его методы. Возьмем класс Car, который мы писали, изучая классы в 14 разделе.
class Car ():
«»»Описание автомобиля»»»
def __init__ ( self , brand, model, years):
«»»Инициализирует атрибуты»»»
self .brand = brand
self .model = model
self .years = years
def read_mileage ( self ):
«»»Пробег автомобиля»»»
print(f»Пробег автомобиля < self .mileage>км.»)
Класс Car описывает автомобиль и содержит метод get_full_name() , который возвращает полное описание автомобиля. Для этого метода и напишем тест, который будет проверять правильно ли возвращается полное описание автомобиля. В тест будем использовать метод assertEqual() , который проверяет равенство.
class TestCar ( unittest.TestCase ):
«»»Тест для класса Car»»»
def test_get_full_name (self):
«»»Проверяет правильность получения полного имени»»»
car_1 = Car(‘audi’, ‘a6’, 2021)
self . assertEqual (car_1. get_full_name() , ‘Автомобиль Audi A6 2021’)
if __name__ == ‘__main__’:
unittest.main()
В начале импортируем модуль unittest . Тестовый сценарий TestCar наследуется от класса unittest.TestCare . В тестовом методе test_get_full_name() мы создаем экземпляр класса Car и передаем ему аргументы. С помощью метода assertEqual() проверяем совпадает ли вызываемый метод в классе Car get_full_name() с теми данными, которые хотим получить. Запустив тест, мы получим результат:
OK
В результате тест прошел успешно.
3. Разные методы assert в Python
Класс unittest.TestCase содержит много проверочных методов assert. Эти методы проверяют выполняются ли условие в определенной точке вашего кода. В таблице перечислены основные методы assert.
| assertEqual (a, b) | Проверяет, что a == b |
| assertNotEqual (a, b) | Проверяет, что a != b |
| assertTrue (x) | Проверяет, что значение x истинно |
| assertFalse (x) | Проверяет, что значение x ложно |
| assertIn (элемент, список) | Проверяет, что элемент входит в список |
| assertNotIn (элемент, список) | Проверяет, что элемент не входит в список |
Приведем пример метода assertNotEqual() класса TestCar из предыдущего примера.
class TestCar(unittest.TestCase):
«»»Тест для класса Car»»»
def test_get_full_name(self):
«»»Проверяет правильность получения полного имени»»»
car_1 = Car(‘audi’, ‘a6’, 2021)
self. assertNotEqual (car_1.get_full_name(), ‘Автомобиль Audi A6 2021’)
if __name__ == ‘__main__’:
unittest.main()
В результате запустив данный тест, мы получим ошибку, так как эти значения равны, и мы проверили это в предыдущем тесте.
F
======================================================================
FAIL: test_get_full_name (__main__.TestCar)
Проверяет правильность получения полного имени
———————————————————————-
Traceback (most recent call last):
File «C:test.py», line 201, in test_get_full_name
self.assertNotEqual(car_1.get_full_name(), ‘Автомобиль Audi A6 2021’)
AssertionError: ‘Автомобиль Audi A6 2021’ == ‘Автомобиль Audi A6 2021’
В описание ошибки видно, что значения равны.
Источник: pythonclass.ru
Модуль unittest: тестируем свои программы
Представьте, что вы написали какую-либо программу, а теперь хотите проверить, правильно ли она работает. Что вы для этого сделаете? Скорее всего, вы запустите её несколько раз с различными входными данными, и убедитесь в правильности выдаваемого ответа.
А теперь вы что-то поменяли и снова хотите проверить корректность программы. Запускать ещё несколько раз? А если потом снова что-то поменяется? Нельзя ли как-то автоматизировать это дело?
Оказывается, можно. В Python встроен модуль unittest, который поддерживает автоматизацию тестов, использование общего кода для настройки и завершения тестов, объединение тестов в группы, а также позволяет отделять тесты от фреймворка для вывода информации.
Для автоматизации тестов, unittest поддерживает некоторые важные концепции:
- Испытательный стенд (test fixture) — выполняется подготовка, необходимая для выполнения тестов и все необходимые действия для очистки после выполнения тестов. Это может включать, например, создание временных баз данных или запуск серверного процесса.
- Тестовый случай (test case) — минимальный блок тестирования. Он проверяет ответы для разных наборов данных. Модуль unittest предоставляет базовый класс TestCase, который можно использовать для создания новых тестовых случаев.
- Набор тестов (test suite) — несколько тестовых случаев, наборов тестов или и того и другого. Он используется для объединения тестов, которые должны быть выполнены вместе.
- Исполнитель тестов (test runner) — компонент, который управляет выполнением тестов и предоставляет пользователю результат. Исполнитель может использовать графический или текстовый интерфейс или возвращать специальное значение, которое сообщает о результатах выполнения тестов.
Модуль unittest предоставляет богатый набор инструментов для написания и запуска тестов. Однако достаточно лишь некоторых из них, чтобы удовлетворить потребности большинства пользователей.
Вот короткий скрипт для тестирования трех методов строк:
Заметьте, что для того, чтобы проверить что-то, мы используем один из assert*() методов.
Тестов может быть много, и часть кода настройки может повторяться. К счастью, мы можем определить код настройки путём реализации метода setUp(), который будет запускаться перед каждым тестом:
Можно разместить все тесты в том же файле, что и сама программа (таком как widgets.py), но размещение тестов в отдельном файле (таком как test_widget.py) имеет много преимуществ:
- Модуль с тестом может быть запущен автономно из командной строки.
- Тестовый код может быть легко отделён от программы.
- Меньше искушения изменить тесты для соответствия коду программы без видимой причины.
- Тестовый код должен изменяться гораздо реже, чем программа.
- Протестированный код может быть легче переработан.
- Тесты для модулей на C должны быть в отдельных модулях, так почему же не быть последовательным?
- Если стратегия тестирования изменяется, нет необходимости изменения кода программы.
Пропуск тестов и ожидаемые ошибки
unittest поддерживает пропуск отдельных тестов, а также классов тестов. Вдобавок, поддерживается пометка теста как «не работает, но так и надо».
Пропуск теста осуществляется использованием декоратора skip() или одного из его условных вариантов.
Декораторы, пропускающие тесты или говорящие об ожидаемых ошибках:
Для пропущенных тестов не запускаются setUp() и tearDown(). Для пропущенных классов не запускаются setUpClass() и tearDownClass(). Для пропущенных модулей не запускаются setUpModule() и tearDownModule().
Различение итераций теста с помощью подтестов
Когда некоторые тесты имеют лишь незначительные отличия, например некоторые параметры, unittest позволяет различать их внутри одного тестового метода, используя менеджер контекста subTest().
Например, следующий тест: