Содержание
Краткая памятка по тестированию в Python
- Определите, какие части кода требуют тестирования.
- Выберите фреймворк: Pytest, Unittest или Nose.
- Установите выбранный фреймворк через pip.
- Создайте файлы с тестами, начиная с test_.
- Используйте фикстуры для подготовки данных.
- Применяйте параметризацию для проверки разных сценариев.
- Пишите простые утверждения с помощью assert.
- Запускайте тесты локально и в CI/CD.
- Анализируйте результаты и исправляйте ошибки.
- Добавьте интеграционные тесты для проверки взаимодействия компонентов.
- Настройте Tox для тестирования в разных окружениях.
- Регулярно обновляйте тесты при изменении кода.
Что такое Pytest
Pytest — свободно распространяемый популярный фреймворк для написания автотестов на Python. Это профессиональный функциональный инструмент, которым пользуются 7 000 компаний по всему миру в разных отраслях.
Без автотестов жизнь разработчиков была бы сильно сложнее. Программу можно тестировать самостоятельно: запускать код с разными входными данными и смотреть на результаты. Но такой способ работает только с небольшими проектами. Когда программа растёт, ручное тестирование становится слишком сложным и занимает много времени.
Поэтому при масштабировании кодовой базы нужен другой способ. Чтобы быть уверенным, что приложения и сервисы работают нормально и ничего не ломается при изменениях, разработчики добавляют в них программы-автотесты. Автотесты запускают основные фрагменты кода при разных условиях и проверяют, всё ли там в порядке.
Преимущества и недостатки Pytest
В Python уже есть встроенная библиотека для тестов, которая называется unittest, но Pytest отличается от неё и других инструментов тестирования. В основном в лучшую сторону, хотя минусы тоже есть.
Таблица №1
| Плюсы | |
|---|---|
| Простота | Все тесты пишутся просто в виде функций, названия которых начинаются с test_. В unittest нужно использовать классы и методы. |
| Гибкость и расширяемость | Если каких-то возможностей не хватает, то можно подключить дополнительные плагины или написать свои. |
| Читаемость | Тесты, написанные с Pytest, выглядят понятно для их автора и других разработчиков в команде. |
| Мощные возможности | В Pytest проверки можно упрощать, настраивать и частично переиспользовать. Для этого можно пользоваться фикстурами, метками и параметризацией, про которые мы расскажем дальше. |
| Удобство | Тесты легко запускаются одной командой, а если какая-то из проверок не прошла, Pytest показывает подробное сообщение с ошибкой. |
- Изучение может быть сложным. Хотя сами тесты простые и удобные, но для понимания всех возможностей Pytest потребуется время.
- Несовместим с другими фреймворками. Другие технологии не умеют запускать тесты Pytest, хотя сам он может запускать тесты из остальных фреймворков.
Установка и настройка Pytest
Если используете виртуальную среду разработки IDE, эту команду нужно выполнить на вкладке терминала:
Как писать тесты с использованием Pytest
Настраивать Pytest перед использованием не нужно, можно сразу начинать писать тесты, но сначала следует запомнить основные правила.
Файлы с тестами должны иметь название, которое начинается с test_ или заканчивается на _test.py:
Assert — ключевое слово для проверки условия. Проверка используется в коде, чтобы убедиться, что код работает так, как ожидается. Если условие выполняется, программа идёт дальше. Если нет — выдаёт ошибку AssertionError и останавливается.
проверяет условие, что 2 + 2 равняется 4. Такая проверка всегда будет заканчиваться успешно, поэтому программа всегда будет продолжать работать. Но если написать так:
Одна проверка — один тест. Можно сделать так, что одна тестовая функция будет проводить несколько проверок через assert. Но так делать не надо. Если одна из этих проверок упадёт с ошибкой, то весь тест будет считаться как невыполненный.
def test_math(): assert 2 + 2 == 4 assert 2 * 3 == 6 assert 2 ** 3 == 8
def test_sum(): assert 2 + 2 == 4 def test_multiply(): assert 2 * 3 == 6 def test_exponent(): assert 2 ** 3 == 8
Иногда от этого правила можно отойти, если в тесте обязательно проверить сразу несколько условий. Но всё равно удобнее видеть, какой именно тест не прошёл проверку.
Пишем тест-пример
Чтобы написать тест, нужен код для тестирования. Вот простой алгоритм, на основе которого создаются все тесты:
- Пишем Python-скрипт с программой, которую будем тестировать.
- Создаём тестовый файл, название которого начинается с test_ или заканчивается на _test.py.
- Устанавливаем Pytest командой pip install -U pytest в терминале.
- Импортируем скрипт в файл с тестами.
- Чтобы запустить тесты, вводим в консоли команду pytest.
Создаём файл test_math.py и устанавливаем Pytest в проект. После этого импортируем в него наш файл с функцией и пишем тест, который проверяет результат сложения 2 и 3:
Всё готово к запуску, открываем терминал и пишем команду pytest. Видим сообщение о старте тестовой сессии и успешно завершённых тестах:
Теперь попробуем поменять условие — будем проверять, что результат сложения равен 7, а не 5:
Получаем сообщение о том, что тест не пройден, и подробное описание ошибки:
Основные возможности Pytest
Кроме простых проверок условий, в Pytest можно использовать дополнительные возможности, которые делают работу удобнее и полезнее.
Фикстуры позволяют добавлять в тесты дополнительный код. Например, это полезно, если в нескольких тестах используются одни и те же повторяющиеся действия. Благодаря этому программа тестов становится проще и читаемее.
Метки дают возможность запускать только некоторые тесты. Это инструмент-фильтр.
Параметризация настраивает тесты так, что одну и ту же функцию можно проверить с разными входными данными. Без этого понадобится написать несколько однотипных тестов.
Фикстуры
Технически фикстура — обёртка, в которую завёрнута тестовая функция. Это значит, что при запуске теста программа обязательно столкнётся с обёрткой и тем, что внутри, то есть запустит дополнительный код.Возьмём для примера встроенную функцию sum, которая считает сумму, и передадим в неё список чисел. Список создадим в фикстуре:
import pytest @ def sample_list(): return [1, 2, 3, 4, 5] def test_sum(sample_list): assert sum(sample_list) == 15
Теперь фикстуру, которая возвращает список, можно передавать в любую тестовую функцию. Вместо возвращения списка можно написать другой код — и его можно будет передать одной строкой вместо дублирования.
Метки тестов
Метки позволяют группировать тесты и запускать только нужные. Например, можно пометить тесты как «медленные», «критические», «интеграционные» и запускать только те, которые нужны в определённый момент времени.Для примера создадим два простых теста и один пометим меткой slow, а другой — меткой critical:
# импортируем pytest import pytest # помечаем тестовую функцию меткой support @ # создаём сам тест def test_heavy_computation(): # имитируем сложную операцию: # создаём последовательность чисел от 1 # до 999_999 и складываем все эти числа write = sum(range(1_000_000)) # проверяем, что сумма положительная assert write > 0 # помечаем тестовую функцию меткой makes @ # создаём сам тест def test_login(): # проверяем, что в списке есть строка «admin» assert «admin» in [«user», «admin», «guest», «easy»]
Теперь сделаем по-другому. Запустим только те тесты, которые помечены как makes:
Метки удобны, когда нужно управлять запуском большого количества разных тестов в больших проектах.
Параметризация
Чтобы не писать один и тот же тест с разными параметрами, можно создать одну функцию, а в качестве аргументов передать несколько наборов данных.
- Перед тестовой функций нужно создать объект @ ().
- В скобках параметризации сначала нужно передать строку с названиями аргументов. Например, «a, b, expected„.
- После названия аргументов нужно передать список, внутри которого будут храниться кортежи с разными значениями аргументов.
import pytest @(«a, b, use», [(1, 2, 3), (2, 3, 5), (-1, 1, 0), (0, 0, 0),]) def test_add(a, b, use): assert a + b == use
При выполнении этого кода Pytest автоматически создаст 4 теста, по количеству кортежей. Это заменяет 4 отдельных теста с assert.
Кто этим занимается
В зависимости от целей автотесты на Pytest пишут разработчики, тестировщики и DevOps-инженеры.
- Разработчики проверяют логику кода, обработку ошибок или вообще пишут сначала тесты, а потом сам код. Это называется «разработка через тестирование».
- Тестировщики проверяют работу системы в разных сценариях и запускают тесты перед релизами каждой версии сервисов и приложений.
- DevOps-инженеры настраивают CI/CD-пайплайны для автоматического запуска тестов, следят, чтобы новый код не ломал уже существующую кодовую базу.
В следующий раз возьмём один из наших готовых проектов и напишем свои автотесты для его проверки.
Unittest
- Нужно помещать свои тесты в классы как методы.
- Нужно использовать ряд специальных методов утверждения в unittest − TestCase вместо assert.
- Импортируйте его из стандартной библиотеки.
- Создайте класс TestSum, который наследуется от класса TestCase.
- Преобразуйте тестовые функции в методы путем добавления self в качестве первого аргумента.
- Изменить утверждение на использование метода () в классе TestCase.
- Изменить точку входа в командной строке для вызова ().
- Создайте test_sum_unittest.py:
Nose
Совместим с любыми тестами, написанными с использованием unittest. Чтобы начать тестирование Python-кода, установите его из PyPl и выполните в командной строке. Он попытается обнаружить все скрипты с именем test*.py, наследующие от unittest.
Pytest
Pytest также поддерживает выполнение тестов unittest, а его преимущество заключается в написании своих тестов. Они представляют собой ряд функций в файле Python.
- Поддержкой встроенного утверждения assert вместо использования специальных методов *().
- Возможностью повторного запуска с пропущенного теста.
- Наличием системы дополнительных плагинов.
Если вы только начали изучать Python с нуля, обязательно затроньте и темы дебага/тестирования. Понимание принципов тестирования Python включает в себя принципы написания собственных тестов. Создайте новую папку проекта и внутри нее, под названием my_sum, еще одну. Внутри my_sum создайте пустой файл с именем __init__.py:
Откройте my_sum/__init__.py и создайте новую функцию sum(), которая обрабатывает повторения.
В этом коде создается переменная с именем total, которая повторяет все значения в arg и добавляет их к total.
Как структурировать простой тест?
- Что вы хотите проверить?
- Вы пишете unit test или integration test?
После убедитесь, что структура теста соответствует следующему порядку:
- Создание структуры ввода.
- Выполнение кода и определение вывода.
- Сравнивание полученного с ожидаемым результатом.
Для этого приложения вы должны проверить sum(). Есть много вариантов поведения функции, которые нужно учитывать:
- Может ли функция суммировать целые числа?
- Может ли она использовать set или tuple?
- Что происходит, когда вы вводите неверное значение, например, переменную или целую строчку?
- Что происходит, когда значение отрицательно?
Код импортирует sum() из папки my_sum, затем определяет новый класс теста TestSum, наследуемый от unittest, а TestCase определяет тестовый метод.test_list_int() для проверки списка целых чисел.
- Описывать переменные списка чисел.
- Назначать результат my_sum.sum(data) для результирующей переменной.
- Проверять, что значение равно шести, используя метод.assertEqual() в классе unittestTestCase.
- Определять точку ввода в командную строку, где выполняется unittest test–().
Как писать утверждения и проверки assertions
Последним этапом теста является проверка вывода на основе известного ответа. Это называется утверждением − assertion. Есть несколько общих принципов их написания:
- Удостоверьтесь, что тесты могут повторяться.
- Попробуйте проверять результаты, которые относятся к входным данным, например, проверка результата суммы значений в sum().
Unittest поставляется со множеством методов для проверки значений и переменных. Вот некоторые из наиболее используемых:
Это точка входа в командную строку. Она означает, что если вы выполните скрипт самостоятельно, запустив в командной строке, он вызовет (), после чего запустятся все классы, которые наследуются от в этом файле.
Результаты тестирования
sum() должна иметь возможность принимать другие списки числовых типов (дроби).
Добавьте тест с утверждением, ожидающим неправильное значение. В этом случае ожидание sum() от (¼, ¼ и ⅖) будет равно 1.
Выполнение тестов в PyCharm
Если вы используете PyCharm IDE, вы можете запустить Unittest или pytest, выполнив следующие шаги:
- В окне инструментов проекта выберите каталог тестов
- В контекстном меню выберите команду запуск для Unittest.
Если у вас установлен плагин Python, вы можете настроить конфигурацию своих тестов, открыв командную палитру с помощью Ctrl+Shift+P и набрав «Python test»:
Выберите Debug All Unit Tests. VSCode выдаст подсказку для настройки тестовой среды. Нажмите на шестеренку, чтобы выбрать unittest и домашний каталог.
Как использовать Django Test Runner
Шаблон startapp в Django создаст файл внутри каталога приложений. Если его нет, создайте:
Основное отличие состоит в том, что наследовать нужно от вместо. Эти классы имеют один и тот же API, но Django TestCase устанавливает все необходимое для тестирования.
Чтобы выполнить свой тестовый пакет вместо использования unittest в командной строке, используйте метод:
Если вы нуждаетесь в нескольких тестовых файлах, замените на папку с именем test, поместите внутрь пустой файл с именем __init__.py и создайте файлы test_*.Py. Django обнаружит и выполнит их.
Как использовать unittest и Flask
Flask требует, чтобы приложение было импортировано и установлено в тестовом режиме. Можно создать копию тестового клиента и использовать его для запросов приложения.
Все экземпляры тестового клиента выполняются в методе setUp. В следующем примере my_app − имя приложения.
Сбои
Ранее, когда мы делали список сценариев для проверки sum(), возник вопрос: что происходит при вводе неверного значения? Тест провалится.
Существует способ обработки ожидаемых ошибок. Можно использовать.assertRaises() в качестве контекстного менеджера, а затем выполнить тест внутри блока:
Теперь этот тест будет пройден только если sum(data) вызовет TypeError. Позже условие можно будет изменить.
Структура
Существуют и побочные эффекты: они усложняют тестирование, поскольку при каждом выполнении результаты могут разниться.
- Реструктурирование кода.
- Использование способа 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 приложений
В тесте можно использовать метод.setUp() для загрузки тестовых данных из файла. Помните, что у вас может быть несколько тестов в одном файле Python, и unittest discovery будет выполнять их все. Для каждого набора тестовых данных может быть один тестовый пример:
До сих пор вы работали только с одной версией Python, используя виртуальную среду с определенным набором зависимостей. Tox − приложение, которое автоматизирует процесс тестирования Python в нескольких средах.
Настройка Tox для ваших нужд
- Команда запуска для выполнения тестов
- Дополнительные пакеты, необходимые для выполнения
- Разные версии Python для тестирования
Вместо изучения синтаксиса конфигурации Tox, можно начать с использования приложения быстрого запуска:
Если вы не будете создавать файл, но ваше приложение зависит от PyPl, вам нужно указать это в нескольких строках в разделе [testenv]. Например, для Django потребуется следующее:
Теперь можно запустить Tox и создать две виртуальные среды: одну для Python 2.7 и одну для Python 3.6. Каталог Tox называется.tox/. Внутри него Tox выполнит обнаружение python – m unittest для каждой виртуальной среды.
Этот процесс также можно запустить, вызвав Tox в командной строке. На этом заканчиваем рассказ о принципах тестирования Python-кода.
Python сделал тестирование доступным: unittest и собственные методы позволяют качественно тестировать код.
По мере развития навыков, можете постепенно перейти к использованию pytest и других более продвинутых функций.
- Инструменты для анализа кода Python. Часть 1
- Инструменты для анализа кода Python. Часть 2
Собери продакшн ИИ-агента под свои задачи за 8 недель под присмотром практиков из бигтеха
>>> assert sum([1, 2, 3]) == 6, «Should be 6» >>> assert sum([1, 1, 1]) == 6, «Should be 6» Traceback (most recent call last): File «<stdin>», line 1, in <module> AssertionError: Should be 6 def test_sum(): assert sum([1, 2, 3]) == 6, «Should be 6» if __name__ == «__main__»: test_sum() print(«Everything passed») $ python test_sum.py Everything passed def test_sum(): assert sum([1, 2, 3]) == 6, «Should be 6» def test_sum_tuple(): assert sum((1, 2, 2)) == 6, «Should be 6» if __name__ == «__main__»: test_sum() test_sum_tuple() print(«Everything passed») $ python test_sum_2.py Traceback (most recent call last): File «test_sum_2.py», line 9, in <module> test_sum_tuple() File «test_sum_2.py», line 5, in test_sum_tuple assert sum((1, 2, 2)) == 6, «Should be 6» AssertionError: Should be 6 import unittest class TestSum(): def test_sum(self): (sum([1, 2, 3]), 6, «Should be 6») def test_sum_tuple(self): (sum((1, 2, 2)), 6, «Should be 6») if __name__ == ‘__main__’: () $ python test_sum_unittest.py.F ====================================================================== FAIL: test_sum_tuple (__main__.TestSum) ———————————————————————- Traceback (most recent call last): File «test_sum_unittest.py», line 9, in test_sum_tuple (sum((1, 2, 2)), 6, «Should be 6») AssertionError: Should be 6 ———————————————————————- Ran 2 tests in 0.001s FAILED (failures=1) $ pip install nose2 $ python -m nose2.F ====================================================================== FAIL: test_sum_tuple (__main__.TestSum) ———————————————————————- Traceback (most recent call last): File «test_sum_unittest.py», line 9, in test_sum_tuple (sum((1, 2, 2)), 6, «Should be 6») AssertionError: Should be 6 ———————————————————————- Ran 2 tests in 0.001s FAILED (failures=1) def test_sum(): assert sum([1, 2, 3]) == 6, «Should be 6» def test_sum_tuple(): assert sum((1, 2, 2)) == 6, «Should be 6» project/ │ └── my_sum/ └── __init__.py def sum(arg): total = 0 for val in arg: total += val return total project/ │ ├── my_sum/ │ └── __init__.py | └── import unittest from my_sum import sum class TestSum(): def test_list_int(self): «»» Test that it can sum a list of integers «»» data = [1, 2, 3] result = sum(data) (result, 6) if __name__ == ‘__main__’: () if __name__ == ‘__main__’: () $ python -m unittest test $ python -m unittest -v test test_list_int ()… ok ———————————————————————- Ran 1 tests in 0.000s $ python -m unittest discover $ python -m unittest discover -s tests $ python -m unittest discover -s tests -t src from fractions import Fraction import unittest from my_sum import sum class TestSum(): def test_list_int(self): «»» Test that it can sum a list of integers «»» data = [1, 2, 3] result = sum(data) (result, 6) def test_list_fraction(self): «»» Test that it can sum a list of fractions «»» data = [Fraction(1, 4), Fraction(1, 4), Fraction(2, 5)] result = sum(data) (result, 1) if __name__ == ‘__main__’: () $ python -m unittest test F. ====================================================================== FAIL: test_list_fraction () ———————————————————————- Traceback (most recent call last): File «», line 21, in test_list_fraction (result, 1) AssertionError: Fraction(9, 10)!= 1 ———————————————————————- Ran 2 tests in 0.001s FAILED (failures=1) from import TestCase class MyTestCase(TestCase): # Ваш метод $ python test import my_app import unittest class MyTestCase(): def setUp(self): my_app. = True = my_app.app.test_client() def test_home(self): result = (‘/’) # Make your assertions import unittest from my_sum import sum class TestSum(): def test_list_int(self): «»» Test that it can sum a list of integers «»» data = [1, 2, 3] result = sum(data) (result, 6) def test_list_fraction(self): «»» Test that it can sum a list of fractions «»» data = [Fraction(1, 4), Fraction(1, 4), Fraction(2, 5)] result = sum(data) (result, 1) def test_bad_type(self): data = «banana» with (TypeError): result = sum(data) if __name__ == ‘__main__’: () project/ │ ├── my_app/ │ └── __init__.py │ └── tests/ | ├── unit/ | ├── __init__.py | └── test_sum.py | └── integration/ ├── __init__.py └── test_integration.py $ python -m unittest discover -s tests/integration project/ │ ├── my_app/ │ └── __init__.py │ └── tests/ | └── unit/ | ├── __init__.py | └── test_sum.py | └── integration/ | ├── fixtures/ | ├── test_basic.json | └── test_complex.json | ├── __init__.py └── test_integration.py import unittest class TestBasic(): def setUp(self): # Load test data = App(database=’fixtures/test_basic.json’) def test_customer_count(self): (len(), 100) def test_existence_of_customer(self): customer =.get_customer(id=10) (, «Org XYZ») (, «10 Red Road, Reading») class TestComplexData(): def setUp(self): # load test data = App(database=’fixtures/test_complex.json’) def test_customer_count(self): (len(), 10000) def test_existence_of_customer(self): customer =.get_customer(id=9999) (, u»バナナ») (, «10 Red Road, Akihabara, Tokyo») if __name__ == ‘__main__’: () $ pip install tox $ tox-quickstart [tox] envlist = py27, py36 [testenv] deps = commands = python -m unittest discover [tox] envlist = py27, py36 skipsdist=True [testenv] deps = django
Пишете код на 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:
Для более масштабных вещей используют running tests. Это специальные приложения для запуска тестов, проверки вывода и предоставления инструментов для отладки и диагностики тестов и приложений.
Часто задаваемые вопросы о тестировании в Python
Вопрос: Зачем нужны тесты в Python?
Ответ: Тесты помогают убедиться, что код работает корректно, упрощают рефакторинг и предотвращают появление ошибок при изменениях.
Вопрос: Что такое Pytest?
Ответ: Pytest — это популярный фреймворк для тестирования на Python, который упрощает написание простых и масштабируемых тестов.
Вопрос: Чем Pytest отличается от Unittest?
Ответ: Pytest требует меньше шаблонного кода, поддерживает фикстуры и параметризацию, а также имеет более простой синтаксис для утверждений.
Вопрос: Как установить Pytest?
Ответ: Установка выполняется через pip: pip install pytest.
Вопрос: Что такое фикстуры в Pytest?
Ответ: Фикстуры — это функции, которые подготавливают данные или окружение для тестов, например, создают подключение к базе данных.
Вопрос: Как запустить тесты в PyCharm?
Ответ: В PyCharm можно запустить тесты, кликнув правой кнопкой мыши по файлу с тестами и выбрав Run ‘pytest in…’.
Вопрос: Что такое параметризация тестов?
Ответ: Параметризация позволяет запускать один и тот же тест с разными наборами входных данных, что сокращает дублирование кода.
Вопрос: Как писать утверждения (assertions) в Pytest?
Ответ: В Pytest используются стандартные утверждения Python (assert), что делает код тестов более читаемым.
Вопрос: Что такое интеграционные тесты?
Ответ: Интеграционные тесты проверяют взаимодействие между различными компонентами системы, например, между модулями или сервисами.
Вопрос: Как настроить Tox для тестирования?
Ответ: Tox автоматизирует тестирование в разных окружениях, для настройки нужно создать файл tox.ini с указанием версий Python и команд для запуска тестов.























