Написание плагинов
Легко реализовать локальные плагины conftest для собственного проекта или плагины, устанавливаемые через pip, которые можно использовать во многих проектах, в том числе сторонних. Если вы хотите только использовать плагины, а не писать их, обратитесь к разделу Как устанавливать и использовать плагины.
Плагин содержит одну или несколько функций-хуков. В разделе Написание хуков объясняются основы и подробности написания собственных функций-хуков. pytest реализует все аспекты настройки, сбора тестов, их выполнения и формирования отчётов, вызывая чётко определённые хуки следующих плагинов:
- встроенные плагины: загружаются из внутреннего каталога
_pytestpytest. - внешние плагины: установленные сторонние модули, обнаруженные через точки входа в метаданных их пакетов
- плагины conftest.py: модули, автоматически обнаруживаемые в каталогах с тестами
В принципе, каждый вызов хука — это вызов функции Python 1:N, где N — количество зарегистрированных функций-реализаций для заданной спецификации. Все спецификации и реализации следуют соглашению об именовании с префиксом pytest_, что позволяет легко различать и находить их.
Порядок обнаружения плагинов при запуске инструмента
pytest загружает модули плагинов при запуске инструмента следующим образом:
- просматривает командную строку в поисках параметра
-p no:nameи блокирует загрузку этого плагина (таким способом можно заблокировать даже встроенные плагины). Это происходит до обычного разбора командной строки. - загружает все встроенные плагины.
- просматривает командную строку в поисках параметра
-p nameи загружает указанный плагин. Это происходит до обычного разбора командной строки. - загружает все плагины, зарегистрированные через точки входа установленных сторонних пакетов, если не задана переменная среды
PYTEST_DISABLE_PLUGIN_AUTOLOAD. - загружает все плагины, указанные в переменной среды
PYTEST_PLUGINS. -
загружает все «начальные» файлы
conftest.py:- определяет пути к тестам: указанные в командной строке; в противном случае — в
testpaths, если параметр задан и запуск выполняется из корневого каталога; в противном случае используется текущий каталог - для каждого пути к тестам загружает
conftest.pyиtest*/conftest.pyотносительно части пути, соответствующей каталогу, если они существуют. Перед загрузкой файлаconftest.pyзагружаются файлыconftest.pyиз всех его родительских каталогов. После загрузки файлаconftest.pyрекурсивно загружаются все плагины, указанные в его переменнойpytest_plugins, если она присутствует.
- определяет пути к тестам: указанные в командной строке; в противном случае — в
conftest.py: локальные плагины для отдельных каталогов
Локальные плагины conftest.py содержат реализации хуков, специфичные для каталогов. При работе с сессией и выполнении тестов вызываются все хуки, определённые в файлах conftest.py, расположенных ближе к корню файловой системы. Ниже приведён пример реализации хука pytest_runtest_setup, который вызывается для тестов в подкаталоге a, но не в других каталогах:
a/conftest.py:
def pytest_runtest_setup(item):
# called for running each test in 'a' directory
print("setting up", item)
a/test_sub.py:
def test_sub():
pass
test_flat.py:
def test_flat():
pass
Вот как его можно запустить:
pytest test_flat.py --capture=no # will not show "setting up" pytest a/test_sub.py --capture=no # will show "setting up"
Примечание
Если у вас есть файлы conftest.py, расположенные вне каталога пакета Python (то есть каталога, содержащего __init__.py), то импорт «import conftest» может быть неоднозначным, поскольку в вашем PYTHONPATH или sys.path могут находиться и другие файлы conftest.py. Поэтому рекомендуется либо размещать conftest.py в области видимости пакета, либо никогда не импортировать ничего из файла conftest.py.
См. также: Механизмы импорта pytest и sys.path/PYTHONPATH.
Примечание
Некоторые хуки нельзя реализовать в файлах conftest.py, которые не являются начальными, из-за особенностей обнаружения плагинов pytest при запуске. Подробности см. в документации к каждому хуку.
Написание собственного плагина
Если вы хотите написать плагин, можно взять за основу множество реальных примеров:
- пример плагина для пользовательского сбора тестов: Простой пример задания тестов в файлах YAML
- встроенные плагины, реализующие функциональность самого pytest
- многочисленные внешние плагины, предоставляющие дополнительные возможности
Все эти плагины реализуют хуки и/или фикстуры, чтобы расширять и дополнять функциональность.
Примечание
Обязательно ознакомьтесь с отличным проектом cookiecutter-pytest-plugin — это шаблон cookiecutter для создания плагинов.
Шаблон станет отличной отправной точкой: в нём есть рабочий плагин, тесты, запускаемые с помощью tox, подробный файл README и предварительно настроенная точка входа.
Также рассмотрите возможность добавить свой плагин в pytest-dev, когда им начнут пользоваться и другие люди.
Как сделать плагин доступным для установки другими пользователями
Если вы хотите сделать свой плагин доступным извне, можно определить так называемую точку входа для дистрибутива, чтобы pytest мог найти модуль плагина. Точки входа — это возможность, предоставляемая инструментами упаковки.
pytest ищет точку входа pytest11, чтобы обнаружить плагины. Поэтому сделать плагин доступным можно, указав его в файле pyproject.toml.
# sample ./pyproject.toml file
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "myproject"
classifiers = [
"Framework :: Pytest",
]
[project.entry-points.pytest11]
myproject = "myproject.pluginmodule"
Если пакет установлен таким образом, pytest загрузит myproject.pluginmodule как плагин, который может определять хуки. Проверьте регистрацию с помощью pytest --trace-config
Примечание
Не забудьте добавить Framework :: Pytest в список классификаторов PyPI, чтобы пользователям было проще найти ваш плагин.
Переписывание assert-выражений
Одна из главных особенностей pytest — использование обычных операторов assert и подробная интроспекция выражений при ошибках проверки утверждений. Это обеспечивается «переписыванием assert-выражений», которое изменяет разобранное AST перед компиляцией в байт-код. Для этого используется хук импорта PEP 302, который устанавливается при запуске pytest и выполняет переписывание при импорте модулей. Однако, поскольку мы не хотим тестировать байт-код, отличный от того, который будет выполняться в рабочей среде, этот хук переписывает только сами тестовые модули (определяемые параметром конфигурации python_files) и модули, входящие в состав плагинов. Другие импортированные модули не переписываются, и проверка утверждений выполняется обычным образом.
Если в других модулях есть вспомогательные функции для проверки утверждений, которым требуется переписывание assert-выражений, нужно явно попросить pytest переписать этот модуль до его импорта.
- register_assert_rewrite(*names)
-
Зарегистрировать одно или несколько имён модулей для переписывания при импорте.
Эта функция гарантирует, что assert-выражения в этом модуле или во всех модулях пакета будут переписаны. Поэтому её нужно вызвать до фактического импорта модуля; если вы пишете плагин, использующий пакет, обычно это делают в __init__.py.
- Параметры:
-
names – Имена модулей для регистрации.
Это особенно важно при написании плагина pytest, созданного с использованием пакета. Хук импорта обрабатывает как плагины только файлы conftest.py и модули, перечисленные в точке входа pytest11. Рассмотрим, например, следующий пакет:
pytest_foo/__init__.py pytest_foo/plugin.py pytest_foo/helper.py
С таким типичным фрагментом setup.py:
setup(..., entry_points={"pytest11": ["foo = pytest_foo.plugin"]}, ...)
В этом случае переписан будет только pytest_foo/plugin.py. Если вспомогательный модуль также содержит assert-выражения, которые нужно переписать, его необходимо пометить соответствующим образом до импорта. Проще всего пометить его для переписывания в модуле __init__.py, который всегда импортируется первым при импорте модуля из пакета. Так plugin.py сможет по-прежнему импортировать helper.py обычным образом. Тогда содержимое pytest_foo/__init__.py должно выглядеть так:
import pytest
pytest.register_assert_rewrite("pytest_foo.helper")
Подключение и загрузка плагинов в тестовом модуле или файле conftest
Плагины можно подключить в тестовом модуле или файле conftest.py с помощью pytest_plugins:
pytest_plugins = ["name1", "name2"]
При загрузке тестового модуля или плагина conftest будут также загружены указанные плагины. В качестве плагина можно использовать любой модуль, в том числе внутренние модули приложения:
pytest_plugins = "myapp.testsupport.myplugin"
Обработка pytest_plugins выполняется рекурсивно. Поэтому учтите: если в приведённом выше примере myapp.testsupport.myplugin также объявляет pytest_plugins, содержимое этой переменной тоже будет загружено как плагины, и так далее.
Примечание
Подключение плагинов с помощью переменной pytest_plugins в файлах conftest.py, не расположенных в корневом каталоге, устарело.
Это важно, потому что файлы conftest.py реализуют хуки для отдельных каталогов, но после импорта плагин влияет на всё дерево каталогов. Чтобы избежать путаницы, объявление pytest_plugins в любом файле conftest.py, который находится не в корневом каталоге тестов, считается устаревшим и вызывает предупреждение.
Этот механизм упрощает совместное использование фикстур внутри приложений и даже между внешними приложениями без необходимости создавать внешние плагины с помощью метаданных точки входа в пакете.
Плагины, импортированные через pytest_plugins, также автоматически помечаются для переписывания assert-выражений (см. pytest.register_assert_rewrite()). Однако это сработает, только если модуль ещё не импортирован. Если на момент обработки инструкции pytest_plugins модуль уже был импортирован, появится предупреждение, а assert-выражения внутри плагина переписаны не будут. Чтобы исправить это, можно самостоятельно вызвать pytest.register_assert_rewrite() до импорта модуля или отложить его импорт до регистрации плагина.
Доступ к другому плагину по имени
Если плагин должен взаимодействовать с кодом другого плагина, он может получить ссылку на него через менеджер плагинов следующим образом:
plugin = config.pluginmanager.get_plugin("name_of_plugin")
Чтобы просмотреть имена существующих плагинов, используйте параметр --trace-config.
Регистрация пользовательских маркеров
Если ваш плагин использует маркеры, зарегистрируйте их, чтобы они отображались в справке pytest и не вызывали ложных предупреждений. Например, следующий плагин зарегистрирует cool_marker и mark_with для всех пользователей:
def pytest_configure(config):
config.addinivalue_line("markers", "cool_marker: this one is for cool tests.")
config.addinivalue_line(
"markers", "mark_with(arg, arg2): this marker takes arguments."
)
Тестирование плагинов
В pytest есть плагин под названием pytester, который помогает писать тесты для кода плагинов. По умолчанию этот плагин отключён, поэтому перед использованием его нужно включить.
Для этого добавьте следующую строку в файл conftest.py в каталоге с тестами:
# content of conftest.py pytest_plugins = ["pytester"]
Также можно запустить pytest с параметром командной строки -p pytester.
Это позволит использовать фикстуру pytester для тестирования кода плагина.
Рассмотрим на примере, что позволяет делать этот плагин. Представим, что мы разработали плагин, предоставляющий фикстуру hello, которая возвращает функцию. Эту функцию можно вызвать с одним необязательным параметром. Если не передать значение, она вернёт строковое значение Hello World!, а если передать строковое значение — Hello
{value}!.
import pytest
def pytest_addoption(parser):
group = parser.getgroup("helloworld")
group.addoption(
"--name",
action="store",
dest="name",
default="World",
help='Default "name" for hello().',
)
@pytest.fixture
def hello(request):
name = request.config.getoption("name")
def _hello(name=None):
if not name:
name = request.config.getoption("name")
return f"Hello {name}!"
return _hello
Теперь фикстура pytester предоставляет удобный API для создания временных файлов conftest.py и файлов тестов. Она также позволяет запускать тесты и возвращает объект результата, с помощью которого можно проверять итоги тестирования.
def test_hello(pytester):
"""Make sure that our plugin works."""
# create a temporary conftest.py file
pytester.makeconftest(
"""
import pytest
@pytest.fixture(params=[
"Brianna",
"Andreas",
"Floris",
])
def name(request):
return request.param
"""
)
# create a temporary pytest test file
pytester.makepyfile(
"""
def test_hello_default(hello):
assert hello() == "Hello World!"
def test_hello_name(hello, name):
assert hello(name) == "Hello {0}!".format(name)
"""
)
# run all tests with pytest
result = pytester.runpytest()
# check that all 4 tests passed
result.assert_outcomes(passed=4)
Кроме того, перед запуском pytest можно скопировать примеры в изолированное окружение pytester. Это позволяет вынести проверяемую логику в отдельные файлы, что особенно полезно для длинных тестов и/или файлов conftest.py.
Обратите внимание: чтобы работал pytester.copy_example, в файле конфигурации необходимо задать pytester_example_dir, указав pytest, где искать файлы с примерами.
# content of pytest.toml [pytest] pytester_example_dir = "."
# content of test_example.py
def test_plugin(pytester):
pytester.copy_example("test_example.py")
pytester.runpytest("-k", "test_example")
def test_example():
pass
$ pytest =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-9.x.y, pluggy-1.x.y rootdir: /home/sweet/project configfile: pytest.toml collected 2 items test_example.py .. [100%] ============================ 2 passed in 0.12s =============================
Дополнительные сведения об объекте результата, который возвращает runpytest(), и предоставляемых им методах см. в документации RunResult.
© 2015–2026 Holger Krekel and pytest-dev team
Licensed under the MIT License.
https://docs.pytest.org/en/stable/how-to/writing_plugins.html