механизмы импорта 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.

Вернуться на верх