механизмы импорта pytest и sys.path/PYTHONPATH¶
Режимы импорта¶
pytest как фреймворк для тестирования должен импортировать тестовые модули и файлы conftest.py для выполнения.
Импорт файлов в Python (по крайней мере, до недавнего времени) - нетривиальный процесс, часто требующий изменения sys.path. Некоторые аспекты процесса импорта можно контролировать с помощью флага командной строки --import-mode, который может принимать такие значения:
prepend(по умолчанию): путь к каталогу, содержащему каждый модуль, будет вставлен в началоsys.path, если его там еще нет, а затем импортирован с помощью встроенного модуля__import__.Это требует, чтобы имена тестовых модулей были уникальными, если дерево тестовых каталогов не организовано в пакеты, поскольку после импорта модули будут помещаться в
sys.modules.Это классический механизм, восходящий к тому времени, когда еще поддерживался Python 2.
append: каталог, содержащий каждый модуль, добавляется в конецsys.path, если его там еще нет, и импортируется с помощью__import__.Это позволяет запускать тестовые модули против установленных версий пакета, даже если тестируемый пакет имеет тот же корень импорта. Например:
testing/__init__.py testing/test_pkg_under_test.py pkg_under_test/
тесты будут работать с установленной версией
pkg_under_testпри использовании--import-mode=append, тогда как при использованииprependони будут брать локальную версию. Именно поэтому мы выступаем за использование макетов src.То же, что и
prepend, требует, чтобы имена тестовых модулей были уникальными, если дерево тестовых каталогов не организовано в пакеты, поскольку после импорта модули будут помещаться вsys.modules.importlib: новый в pytest-6.0, этот режим используетimportlibдля импорта тестовых модулей. Это дает полный контроль над процессом импорта и не требует измененияsys.path.По этой причине здесь не требуется, чтобы имена тестовых модулей были уникальными.
Однако один недостаток заключается в том, что тестовые модули не импортируются друг другом. Кроме того, служебные модули в каталогах tests не импортируются автоматически, поскольку каталог tests больше не добавляется в
sys.path.Изначально мы планировали сделать
importlibпо умолчанию в будущих релизах, однако теперь ясно, что у него есть свои недостатки, поэтому в обозримом будущем по умолчанию останетсяprepend.
См.также
Переменная конфигурации pythonpath.
Сценарии режимов импорта prepend и append¶
Вот список сценариев при использовании режимов импорта prepend или append, в которых pytest должен изменить sys.path для импорта тестовых модулей или conftest.py файлов, и проблемы, с которыми пользователи могут столкнуться из-за этого.
Тестовые модули / conftest.py файлы внутри пакетов¶
Рассмотрим следующее расположение файлов и каталогов:
root/
|- foo/
|- __init__.py
|- conftest.py
|- bar/
|- __init__.py
|- tests/
|- __init__.py
|- test_foo.py
При выполнении:
pytest root/
pytest найдет foo/bar/tests/test_foo.py и поймет, что это часть пакета, учитывая, что в той же папке есть файл __init__.py. Затем он будет искать вверх, пока не найдет последнюю папку, которая все еще содержит файл __init__.py, чтобы найти корень пакета (в данном случае foo/). Чтобы загрузить модуль, он вставит root/ перед sys.path (если его там еще нет), чтобы загрузить test_foo.py как модуль foo.bar.tests.test_foo.
Та же логика применима к файлу conftest.py: он будет импортирован как модуль foo.conftest.
Сохранение полного имени пакета важно, когда тесты живут в пакете, чтобы избежать проблем и позволить тестовым модулям иметь дублирующиеся имена. Это также подробно обсуждается в Соглашения для обнаружения тестов в Python.
Автономные модули тестирования / файлы conftest.py¶
Рассмотрим следующее расположение файлов и каталогов:
root/
|- foo/
|- conftest.py
|- bar/
|- tests/
|- test_foo.py
При выполнении:
pytest root/
pytest найдет foo/bar/tests/test_foo.py и поймет, что он НЕ является частью пакета, учитывая, что в той же папке нет файла __init__.py. Затем он добавит root/foo/bar/tests к sys.path, чтобы импортировать test_foo.py как модуль test_foo. То же самое делается с файлом conftest.py путем добавления root/foo к sys.path, чтобы импортировать его как conftest.
По этой причине в данном макете не может быть тестовых модулей с одинаковым именем, так как все они будут импортированы в глобальное пространство имен импорта.
Это также подробно обсуждается в Соглашения для обнаружения тестов в Python.
Вызов pytest в отличие от python -m pytest¶
Запуск pytest с pytest [...] вместо python -m pytest [...] приводит к почти эквивалентному поведению, за исключением того, что последний добавит текущий каталог в sys.path, что является стандартным поведением python.
См. также Вызов pytest через python -m pytest.