Pytest — фреймворк для автоматизированного тестирования на Python (обзорно мы знакомились с ним в 01.02 - Знакомство с AQA).
Начнем мы с создания простого теста, построим представление, а после уже будем углубляться в различные особенности этого фреймворка.
Создание тестового файла
Tip
Pytest автоматически обнаруживает тестовые файлы и функции, следуя определенным соглашениям об именовании. Тестовые файлы должны именоваться следующим образом:
test_*.py (например, test_my_functions.py)
_test.py (например, my_module_test.py)
Создайте файл, test_addition.py. В этом файле мы будем писать наши тесты.
Написание простой тестовой функции
Note
Тестовые функции в pytest должны начинаться с префикса test_. Это позволяет pytest автоматически находить и запускать их. Давайте напишем простую функцию, которая проверяет сложение двух чисел:
def test_addition(): — определение тестовой функции. Имя функции начинается с test_, что является обязательным для pytest.
assert 2 + 2 == 4 — это утверждение (assertion). Утверждения используются для проверки ожидаемых результатов. В данном случае мы утверждаем, что сумма 2 и 2 равна 4. Если это условие истинно, тест считается пройденным. Если условие ложно, тест считается проваленным.
Tip
Если вы все сделали правильно, то у вас должен был появиться запуск тестовой функции! как на скриншоте ниже (для pycharm)
🧪Практика:
Example
Сделать файл, тестовую функциюю.
Запустить тестовую функцию
Изменить результат выражения assert, чтобы возникла ошибка. Ознакомиться с исключением
Вспоминаем про использование операторов assert
Note
Оператор assert — ключевой инструмент в тестировании. Он принимает логическое выражение и проверяет его истинность. Если выражение истинно, выполнение теста продолжается. Если выражение ложно, тест завершается с ошибкой AssertionError.
Вот несколько примеров использования assert:
assert a == b: Проверяет, что a равно b
assert a != b: Проверяет, что a не равно b
assert a > b, assert a < b, assert a >= b, assert a <= b: Проверяют отношения “больше”, “меньше”, “больше или равно”, “меньше или равно”
assert x in y: Проверяет, что x содержится в y (например, x является элементом списка или подстрокой в строке)
assert x not in y: Проверяет, что x не содержится в y
Note
Можно добавлять информативное сообщение к assert, которое будет выводиться в случае провала теста:
def test_string_contains(): text = "hello world" substring = "world" assert substring in text, f"Подстрока '{substring}' не найдена в строке '{text}'"
Запуск тестов из командной строки
Note
Для запуска тестов с помощью pytest необходимо открыть терминал или командную строку, перейти в директорию, содержащую тестовые файлы, и выполнить команду:
Скриншот на всякий случай
pytest
Note
Pytest автоматически обнаружит все файлы, начинающиеся с test_ или заканчивающиеся на _test.py, и запустит все функции, начинающиеся с test_.
Можно указать конкретный файл или даже конкретную функцию для запуска:
pytest test_addition.py: Запустить тесты только из файла test_addition.py.
pytest test_addition.py::test_addition: Запустить только функцию test_addition из файла test_addition.py
Note
Про правила запуска мы еще успеем отдельно поговорить, на текущий момент этого будет достаточно для практики
Разбор вывода pytest
После запуска тестов pytest выводит результаты в терминале. Рассмотрим основные элементы вывода:
. (точка): Тест пройден успешно.
F (Failing): Тест провален.
E (Error): Во время выполнения теста произошла ошибка (например, исключение).
s (skipped): Тест был пропущен (например, с помощью маркировки @pytest.mark.skip)
x (xfailed): Тест ожидаемо провален (с помощью маркировки @pytest.mark.xfail).
Note
В случае проваленных тестов (F или E) pytest выводит подробную информацию об ошибке, включая место, где произошло исключение, и сообщение об ошибке (если оно было добавлено к assert)
Вывод будет примерно таким (в зависимости от вашей версии pytest):
(.venv) PS C:\Users\Николай\PycharmProjects\lessons> pytest========================================================== test session starts ==========================================================platform win32 -- Python 3.10.4, pytest-8.3.4, pluggy-1.5.0rootdir: C:\Users\Николай\PycharmProjects\lessonscollected 5 items test_addition.py . [ 20%]test_my_functions.py ...F [100%]=============================================================== FAILURES ================================================================____________________________________________________________ test_add_string ____________________________________________________________ def test_add_string():> assert add("test", "string") == "test string" #тест который должен упастьE AssertionError: assert 'teststring' == 'test string'EE - test stringE ? -E + teststringtest_my_functions.py:19: AssertionError======================================================== short test summary info ========================================================FAILED test_my_functions.py::test_add_string - AssertionError: assert 'teststring' == 'test string'====================================================== 1 failed, 4 passed in 0.07s ======================================================
Tip
Этот вывод показывает, что три теста прошли успешно (.), а один тест (test_add_string) провалился (F), и предоставляет подробную информацию о причине провала
🧪Практика:
Example
Повторить то же самое написав Pytest в терминал
Запустить сделать так, чтобы все тесты прошли - посмотреть на вывод
Изменить 2 тестовые функции, чтобы они упали. Запустить тесты и ознакомиться как выглядит падение нескольких тестов
Тестовый класс
Note
В pytest, помимо написания отдельных тестовых функций, можно использовать тестовые классы, что позволит нам сгруппировать тесты, относящиеся к одному классу или функциональности
Создание тестового класса
Tip
Тестовый класс создается как обычный класс Python, но с одним важным условием: его имя должно начинаться с префикса Test. Например, TestMyClass, TestUserRegistration. Методы внутри тестового класса, которые являются тестами, должны начинаться с префикса test_
Возьмем наш тестовый модуль, test_my_functions.py и сделаем там тестовый класс:
Слева напротив class TestMyFunctions: появилась возможность запуска тестов этого класса
🧪Практика:
Example
Преобразовать ваш предыдущий тест, использовав в нем тестовый класс
Попробовать запустить тестовый класс напрямую
Запустить тесты используя терминал и команду pytest . Проанализировать вывод
Запустить тесты класса напрямую через терминал. Пример: pytest test_my_functions.py::TestMyFunctions
Fixtures (фикстуры):
Note
Фикстуры — это функции, которые предоставляют данные или ресурсы для тестов. Они выполняют настройку (setup) перед выполнением теста и, опционально, очистку (teardown) после его завершения
Создаем фикстуру
Для создания фикстуры используется декоратор @pytest.fixture.
Пример:
import pytest@pytest.fixturedef input_data(): return [1, 2, 3, 4, 5]def test_sum(input_data): #используем фикстуру как аргумент функции assert sum(input_data) == 15def test_len(input_data): #используем фикстуру как аргумент функции assert len(input_data) == 5
Tip
В этом примере:
Обратите внимание, что мы импортировали pytest: import pytest
@pytest.fixture: Декоратор, который преобразует функцию input_data в фикстуру
def input_data():: Функция, которая возвращает список чисел. Этот список будет доступен в тестах, использующих фикстуру
Фикстуры передаются в тестовую функцию в виде аргументов (в эти вот скобочки)
def test_sum(input_data): и def test_len(input_data):: Тестовые функции, которые принимают фикстуру input_data в качестве аргумента. Pytest автоматически передаст результат выполнения фикстуры в эти функции
Область видимости фикстур (Scope)
Фикстуры могут иметь разную область видимости (scope), которая определяет, как часто фикстура будет создаваться и уничтожаться. Доступные области видимости:
function (по умолчанию): Фикстура создается для каждого тестового метода.
class: Фикстура создается один раз для всех тестовых методов в классе.
module: Фикстура создается один раз для всех тестов в модуле (файле).
session: Фикстура создается один раз для всей тестовой сессии (то есть прогон тестов начался - фикстура для всех)
Note
Область видимости задается с помощью параметра scope декоратора @pytest.fixture
Фикстуры в pytest работают по принципу отложенного выполнения и зависимости. Это означает, что фикстура не выполняется, пока она явно не запрошена тестом.
Note
Запрос фикстуры: Тест запрашивает фикстуру, указывая её имя в качестве аргумента функции теста
Поиск и выполнение: Pytest ищет фикстуру с указанным именем. Если фикстура не найдена в текущем файле, pytest ищет её в файле conftest.py (который может находиться в корне проекта или в любой из его поддиректорий). После нахождения фикстура выполняется
Кэширование: Результат выполнения фикстуры кэшируется. Это значит, что если несколько тестов запрашивают одну и ту же фикстуру с одинаковой областью видимости, фикстура будет выполнена только один раз, а затем её кэшированный результат будет передан во все запрашивающие тесты.
Жизненный цикл (создание и уничтожение):
Фикстура создается (выполняется код функции фикстуры) непосредственно перед первым тестом, который её запрашивает, в пределах её области видимости.
Фикстура “уничтожается” (выполняется код после yield, если он есть) после последнего теста, который её использовал, в пределах её области видимости. Если yield отсутствует, фикстура считается “уничтоженной” сразу после возврата значения
Tip
Здесь вы увидели 2 незнакомых слова: это файл conftest.py и yield
conftest.py - это будет отдельный файл, который будет хранить фикстуры, его разберем дальше
yield - тоже будем дальше разбирать этот оператор, пока не обращаем на него внимания
Conftest
Tip
conftest.py — это специальный файл в pytest, который служит для хранения фикстур и плагинов, доступных для нескольких тестовых файлов. Pytest автоматически обнаруживает этот файл и использует определенные в нем фикстуры без необходимости их импортировать в тестовые файлы
Размещение:
Note
Файл conftest.py может быть размещен в любой директории вашего проекта. Фикстуры, определенные в conftest.py, будут доступны для всех тестов в этой директории и во всех ее поддиректориях.
Пример:
Предположим, у нас есть следующая структура проекта:
В файле conftest.py мы можем определить фикстуру, которая будет использоваться во всех тестовых файлах, поскольку она находится в корне проекта
Пример с разными уровнями conftest.py:
Tip
Если у вас есть несколько уровней вложенности директорий с тестами, вы можете использовать несколько файлов conftest.py. Фикстуры будут доступны в пределах директории, где находится conftest.py, и во всех ее поддиректориях.
> `my_project/
├── tests/
│ ├── unit/
│ │ ├── test_math.py
│ │ └── conftest.py # Фикстуры для unit тестов
│ └── integration/
│ └── test_database.py
└── conftest.py # Фикстуры для всех тестов`
>
>
> В этом случае фикстуры из корневого `conftest.py` будут доступны для всех тестов, а фикстуры из `tests/unit/conftest.py` будут доступны только для тестов в директории `unit` и ее поддиректориях.
>
Note
Важные моменты про conftest.py:
Фикстуры, определенные в conftest.py, не нужно импортировать в тестовые файлы.
conftest.py автоматически обнаруживается pytest.
Можно использовать несколько файлов conftest.py для организации фикстур по разным уровням вложенности
🧪Практика:
Example
Взять пример из урока:
import pytest@pytest.fixturedef input_data(): return [1, 2, 3, 4, 5]def test_sum(input_data): #используем фикстуру как аргумент функции assert sum(input_data) == 15def test_len(input_data): #используем фикстуру как аргумент функции assert len(input_data) == 5
Задача - сделать в корне проекта файл conftest.py
Перенести фикстуру в только что созданный файл
@pytest.fixturedef input_data():
Запустить тест. Все должно сработать
FAQ если не работает:
Если вы положили все правильно, но пишет что фикстура не найдена, то лезем в конфигурацию запуска
правой кнопкой мыши на запуск → Modify Run Configuration
скрин
смотрим на его working directory. Здесь должна быть корневая директория, чтобы был виден conftest из корня
скрин
Если это не так, то меняем на корневую и проверяем запуск.
Например у меня
C:\Users\Николай\PycharmProjects\lessons\tests - было
C:\Users\Николай\PycharmProjects\lessons - стало
Попробовать запустить. Если сработало, идем дальше
Меняем теймплейт (шаблон) для создания конфигураций.
Заходим в настройки конфигурации запуска
скрин
Заходим в edit configuration templates
скрин
Устанавливаем рабочую директорию для autodetect
ОБЯЗАТЕЛЬНО жмем apply
скрин
Теперь НОВЫЕ! созданные тесты будут смотреть в корневую директорию и найдут conftest при запуске
Повторим еще раз (или шпаргалка) по запуску Pytest
Запуск всех тестов в текущей директории и поддиректориях:
pytest
(Запускается из корня проекта my_project)
Запуск всех тестов в конкретной директории (папки):