Погружаемся в основы и нюансы тестирования Python-кода

3
6732
Добавить в избранное

Пишете код на Python? Будет полезно знать о принципах тестирования Python-кода ваших приложений. Изучайте статью и применяйте навыки в работе.

Многие считают что язык программирования Python − это просто. Такое впечатление складывается после прочитанной книги по Python, статьи или видео-туториала. Возможно, он действительно проще, чем другие технологии, вот только без трудностей не бывает даже тут. Но и их можно избежать, если понять принципы тестирования Python-кода.

Как всё устроено

Сразу к делу. Вот как будет проходить проверка функции sum() (1,2,3) равна шести:

Тест не выведет ничего на REPL, так как значения верны. Но если результат sum() неверен, это приведет к ошибке AssertionError и сообщению “Should be 6”.

В REPL вы видите AssertionError, потому что результат не соответствует 6. Переместите код в новый файл, названный test_sum.py и выполните снова:

Вы написали пример теста, утверждение и точку входа.

sum() принимает любое повторяющееся значение в качестве первого аргумента. Вы проверили список, теперь проверьте так же и tuple. Создайте новый файл test_sum_2.py:

Когда вы выполняете test_sum_2.py, скрипт выдает ошибку, так как sum() от (1,2,2) не равна 6:

Для более масштабных вещей используют running tests. Это специальные приложения для запуска тестов, проверки вывода и предоставления инструментов для отладки и диагностики тестов и приложений.

Выбор Test Runner

Unittest

Unittest содержит как структуру тестирования Python, так и test runners. У него есть несколько требований:

  • Нужно помещать свои тесты в классы как методы.
  • Нужно использовать ряд специальных методов утверждения в unittest − TestCase вместо assert.

Для преобразования в unittest:

  • Импортируйте его из стандартной библиотеки.
  • Создайте класс TestSum, который наследуется от класса TestCase.
  • Преобразуйте тестовые функции в методы путем добавления self в качестве первого аргумента.
  • Изменить утверждение на использование метода self.assertEqual() в классе TestCase.
  • Изменить точку входа в командной строке для вызова unittest.main().
  • Создайте test_sum_unittest.py:

Nose

Совместим с любыми тестами, написанными с использованием unittest. Чтобы начать тестирование Python-кода, установите его из PyPl и выполните в командной строке. Он попытается обнаружить все скрипты с именем test*.py, наследующие от unittest.

Pytest

Pytest также поддерживает выполнение тестов unittest, а его преимущество заключается в написании своих тестов. Они представляют собой ряд функций в файле Python.

Кроме того, он отличается:

  • Поддержкой встроенного утверждения assert вместо использования специальных методов self.assert*().
  • Возможностью повторного запуска с пропущенного теста.
  • Наличием системы дополнительных плагинов.

Написание тестового примера TestSum для pytest будет выглядеть так:

Написание вашего первого теста

Если вы только начали изучать Python с нуля, обязательно затроньте и темы дебага/тестирования. Понимание принципов тестирования Python включает в себя принципы написания собственных тестов. Создайте новую папку проекта и внутри нее, под названием my_sum, еще одну. Внутри my_sum создайте пустой файл с именем __init__.py:

Откройте my_sum/__init__.py и создайте новую функцию sum(), которая обрабатывает повторения.

В этом коде создается переменная с именем total, которая повторяет все значения в arg и добавляет их к total.

Где писать тест

Создайте в корне файл test.py, который будет содержать ваш первый тест:

Как структурировать простой тест?

Прежде чем перейти к написанию тестов, вы должны понять следующее:

  • Что вы хотите проверить?
  • Вы пишете unit test или integration test?

После убедитесь, что структура теста соответствует следующему порядку:

  • Создание структуры ввода.
  • Выполнение кода и определение вывода.
  • Сравнивание полученного с ожидаемым результатом.

Для этого приложения вы должны проверить sum(). Есть много вариантов поведения функции, которые нужно учитывать:

  • Может ли функция суммировать целые числа?
  • Может ли она использовать set или tuple?
  • Что происходит, когда вы вводите неверное значение, например, переменную или целую строчку?
  • Что происходит, когда значение отрицательно?

Начнем с суммы целых чисел.

Код импортирует sum() из папки my_sum, затем определяет новый класс теста TestSum, наследуемый от unittest, а TestCase определяет тестовый метод .test_list_int() для проверки списка целых чисел.

Метод .test_list_int() будет:

  • Описывать переменные списка чисел.
  • Назначать результат my_sum.sum(data) для результирующей переменной.
  • Проверять, что значение равно шести, используя метод .assertEqual() в классе unittestTestCase.
  • Определять точку ввода в командную строку, где выполняется unittest test–runner .main().

Как писать утверждения и проверки assertions

Последним этапом теста является проверка вывода на основе известного ответа. Это называется утверждением − assertion. Есть несколько общих принципов их написания:

  • Удостоверьтесь, что тесты могут повторяться.
  • Попробуйте проверять результаты, которые относятся к входным данным, например, проверка результата суммы значений в sum().

Unittest поставляется со множеством методов для проверки значений и переменных. Вот некоторые из наиболее используемых:

Проверка Test Runners

Это точка входа в командную строку. Она означает, что если вы выполните скрипт самостоятельно, запустив python.test.py в командной строке, он вызовет unittest.main(), после чего запустятся все классы, которые наследуются от unittest.TestCase в этом файле.

Вы можете предоставить дополнительные опции для изменения вывода. Один из них – “–v”:

Вместо предоставления имени модуля, содержащего тесты, можно запросить автоматическое обнаружение:

Если у вас есть несколько тестов, и вы следуете шаблону test*.py, можно указать имя каталога, используя –s flag:

Если исходный код отсутствует в корне каталога и содержится в подкаталоге, можно сообщить Unittest, где выполнить тесты, чтобы он правильно импортировал модули с –t flag:

Результаты тестирования

sum() должна иметь возможность принимать другие списки числовых типов (дроби).

В верхней части файла test.py добавьте оператор импорта:

Добавьте тест с утверждением, ожидающим неправильное значение. В этом случае ожидание sum() от (¼, ¼ и ⅖) будет равно 1.

Если вы снова выполните тест с python –m unittest test, вы увидите следующее:

Выполнение тестов в PyCharm

Если вы используете PyCharm IDE, вы можете запустить Unittest или pytest, выполнив следующие шаги:

  • В окне инструментов проекта выберите каталог тестов
  • В контекстном меню выберите команду запуск для Unittest.

PyCharm Testing

Выполнение тестов из кода Visual Studio

Если у вас установлен плагин Python, вы можете настроить конфигурацию своих тестов, открыв командную палитру с помощью Ctrl+Shift+P и набрав «Python test»:Visual Studio Code Step 1

Выберите Debug All Unit Tests. VSCode выдаст подсказку для настройки тестовой среды. Нажмите на шестеренку, чтобы выбрать unittest и домашний каталог.

Visual Studio Code Step 2

Тестирование для Django и Flask

Как использовать Django Test Runner

Шаблон startapp в Django создаст файл test.py внутри каталога приложений. Если его нет, создайте:

Основное отличие состоит в том, что наследовать нужно от django.test.TestCase вместо unittest.TestCase. Эти классы имеют один и тот же API, но Django TestCase устанавливает все необходимое для тестирования.

Чтобы выполнить свой тестовый пакет вместо использования unittest в командной строке, используйте метод manage.py:

Если вы нуждаетесь в нескольких тестовых файлах, замените test.py на папку с именем test, поместите внутрь пустой файл с именем __init__.py и создайте файлы test_*.Py. Django обнаружит и выполнит их.

Как использовать unittest и Flask

Flask требует, чтобы приложение было импортировано и установлено в тестовом режиме. Можно создать копию тестового клиента и использовать его для запросов приложения.

Все экземпляры тестового клиента выполняются в методе setUp. В следующем примере my_app − имя приложения.

Сложные сценарии тестирования

Сбои

Ранее, когда мы делали список сценариев для проверки sum(), возник вопрос: что происходит при вводе неверного значения? Тест провалится.

Существует способ обработки ожидаемых ошибок. Можно использовать .assertRaises() в качестве контекстного менеджера, а затем выполнить тест внутри блока:

Теперь этот тест будет пройден только если sum(data) вызовет TypeError. Позже условие можно будет изменить.

Структура

Существуют и побочные эффекты: они усложняют тестирование, поскольку при каждом выполнении результаты могут разниться.

Основы тестирования Python

Спасительные методы:

  • Реструктурирование кода.
  • Использование способа mocking для методов функции.
  • Использование integration test вместо unit test.

Написание integration tests

До этого времени мы занимались в основном unit testing. Двигаемся дальше.

Integration testing – тестирование нескольких компонентов приложения для проверки их совместной работоспособности. Integration testing может требовать разные сценарии работы:

  • Вызов HTTP REST API
  • Вызов Python API
  • Вызов веб–службы
  • Запуск командной строки

Каждый из этих типов integration tests может быть записан так же, как и unit test. Существенное отличие состоит в том, что Integration tests проверяют сразу несколько компонентов. Можно разделить тесты на integration и unit − разбить их по папкам:

Можно указать путь к тестам:

Тестирование data-driven приложений

Многие integration tests требуют базовые данные, содержащие определенные значения. Например, может потребоваться тест, который проверяет правильность отображения приложения с более чем 100 клиентами в базе данных, написанной на японском.

Хорошим решением будет хранение тестовых данных в отдельной папке под названием «fixtures», чтобы указать, где именно содержится нужная информация.

Вот пример этой структуры, если данные состоят из файлов JSON:

В тесте можно использовать метод .setUp() для загрузки тестовых данных из файла. Помните, что у вас может быть несколько тестов в одном файле Python, и unittest discovery будет выполнять их все. Для каждого набора тестовых данных может быть один тестовый пример:

Тестирование в нескольких средах

До сих пор вы работали только с одной версией Python, используя виртуальную среду с определенным набором зависимостей. Tox − приложение, которое автоматизирует процесс тестирования Python в нескольких средах.

Установка Tox

Настройка Tox для ваших нужд

Tox настраивается через файл конфигурации в каталоге проекта. Он содержит следующее:

  • Команда запуска для выполнения тестов
  • Дополнительные пакеты, необходимые для выполнения
  • Разные версии Python для тестирования

Вместо изучения синтаксиса конфигурации Tox, можно начать с использования приложения быстрого запуска:

Средство конфигурации Tox создаст файл, похожий на следующий в tox.ini:

Прежде чем запустить Tox, нужно создать файл setup.py, который будет содержать порядок установки пакета.

Вместо этого, можно добавить строку в файл tox.ini в заголовке [tox]:

Если вы не будете создавать файл setup.py, но ваше приложение зависит от PyPl, вам нужно указать это в нескольких строках в разделе [testenv]. Например, для Django потребуется следующее:

Теперь можно запустить Tox и создать две виртуальные среды: одну для Python 2.7 и одну для Python 3.6. Каталог Tox называется .tox/. Внутри него Tox выполнит обнаружение python – m unittest для каждой виртуальной среды.

Этот процесс также можно запустить, вызвав Tox в командной строке. На этом заканчиваем рассказ о принципах тестирования Python-кода.

Заключение

Python сделал тестирование доступным: unittest и собственные методы позволяют качественно тестировать код.

По мере развития навыков, можете постепенно перейти к использованию pytest и других более продвинутых функций.

Понравился материал об основах тестирования Python-кода? Возможно, вас заинтересует следующее:

Источник: Основы тестирования Python on Realpython

Интересуетесь программированием на Python?

Подпишитесь на нашу рассылку, чтобы получать больше интересных материалов:

И не беспокойтесь, мы тоже не любим спам. Отписаться можно в любое время.




3 Комментарии

  1. ошибка: опечатка
    «Когда вы выполняете test_sum_2.py, скрипт выдает ошибку, так как sum() от (1,2,3) не равна 6:»
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^

  2. Почему не упомянуто про unittest.mock и не приведен пример? Начал замечать, что пишите статьи тяп-ляп по верхам для самых нубасов

Оставьте комментарий