Механизмы импорта pytest и sys.path/PYTHONPATH
Режимы импорта
pytest как среде тестирования необходимо импортировать тестовые модули и файлы conftest.py для выполнения.
Импорт файлов в Python — нетривиальный процесс, поэтому отдельными аспектами импорта можно управлять с помощью флага командной строки --import-mode, который может принимать следующие значения:
-
prepend(по умолчанию): путь к каталогу, содержащему каждый модуль, будет вставлен в началоsys.path, если его там ещё нет, после чего модуль будет импортирован с помощью функцииimportlib.import_module.Настоятельно рекомендуется организовать тестовые модули в виде пакетов, добавив файлы
__init__.pyв каталоги с тестами. Это позволит включить тесты в полноценный пакет Python, благодаря чему pytest сможет определить их полное имя (например,tests.core.test_coreдляtest_core.pyв пакетеtests.core).Если дерево каталогов с тестами не организовано в виде пакетов, имена всех тестовых файлов должны быть уникальными, иначе pytest выдаст ошибку, если обнаружит два теста с одинаковыми именами.
Это классический механизм, существующий со времён, когда Python 2 ещё поддерживался.
-
append: каталог, содержащий каждый модуль, будет добавлен в конецsys.path, если его там ещё нет, после чего модуль будет импортирован с помощьюimportlib.import_module.Этот режим позволяет пользователям запускать тестовые модули для установленных версий пакета, даже если корень импорта тестируемого пакета совпадает. Например:
testing/__init__.py testing/test_pkg_under_test.py pkg_under_test/
тесты будут выполняться для установленной версии
pkg_under_testпри использовании--import-mode=append, тогда как при использованииprependони будут использовать локальную версию. Именно из-за подобных неоднозначностей мы рекомендуем использовать структуру каталогов src.Как и
prepend, этот режим требует уникальных имён тестовых модулей, если дерево каталогов с тестами не организовано в виде пакетов, поскольку после импорта модули будут помещены вsys.modules.
-
importlib: в этом режиме для импорта тестовых модулей используются более гибкие механизмы, предоставляемыеimportlib, без измененияsys.path.Преимущества этого режима:
- pytest совсем не изменяет
sys.path. - Имена тестовых модулей не обязательно должны быть уникальными — pytest автоматически сгенерирует уникальное имя на основе
rootdir.
Недостатки:
- Тестовые модули не могут импортировать друг друга.
-
Вспомогательные модули для тестирования в каталогах с тестами (например, модуль
tests.helpersс функциями/классами, связанными с тестированием) импортировать нельзя. В этом случае рекомендуется разместить вспомогательные модули для тестирования рядом с кодом приложения/библиотеки, например вapp.testing.helpers.Важно: под «вспомогательными модулями для тестирования» мы подразумеваем функции/классы, которые непосредственно импортируются другими тестами; это не относится к фикстурам, которые следует размещать в файлах
conftest.pyвместе с тестовыми модулями. pytest обнаруживает их автоматически.
Это работает следующим образом:
-
Для заданного пути к модулю, например
tests/core/test_models.py, выводится каноническое имя, напримерtests.core.test_models, после чего выполняется попытка импорта.Для модулей, не являющихся тестовыми, это сработает, если они доступны через
sys.path. Так, например,.env/lib/site-packages/app/core.pyможно будет импортировать какapp.core. Это происходит, когда плагины импортируют модули, не являющиеся тестовыми (например, при тестировании документации).Если этот шаг выполнится успешно, модуль будет возвращён.
Для тестовых модулей этот шаг завершится неудачей, если они недоступны через
sys.path. -
Если предыдущий шаг завершился неудачей, мы импортируем модуль напрямую с помощью средств
importlib, что позволяет импортировать его, не изменяяsys.path.Поскольку Python требует, чтобы модуль также был доступен в
sys.modules, pytest выводит для него уникальное имя на основе его относительного расположения относительноrootdirи добавляет модуль вsys.modules.Например,
tests/core/test_models.pyв итоге будет импортирован как модульtests.core.test_models.
Добавлено в версии 6.0.
- pytest совсем не изменяет
Примечание
Изначально мы планировали сделать importlib режимом по умолчанию в будущих выпусках, однако теперь очевидно, что у него есть свои недостатки, поэтому в обозримом будущем режимом по умолчанию останется prepend.
Примечание
По умолчанию pytest не пытается автоматически разрешать пакеты пространств имён, но это поведение можно изменить с помощью переменной конфигурации consider_namespace_packages.
Сценарии режимов импорта 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. Затем pytest будет подниматься по каталогам вверх, пока не найдёт последний каталог, в котором всё ещё есть файл __init__.py, чтобы определить корень пакета (в данном случае foo/). Для загрузки модуля pytest вставит 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. Затем pytest добавит 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.
© 2015–2026 Holger Krekel and pytest-dev team
Licensed under the MIT License.
https://docs.pytest.org/en/stable/explanation/pythonpath.html