Я пытаюсь использовать Pytest для тестирования крупного проекта (~ 100k LOC, 1k файлов), и есть несколько других похожих проектов, для которых мне бы хотелось в конечном итоге сделать то же самое. Это не стандартный пакет Python ; это часть сильно настроенной системы, которую я мало что могу изменить, по крайней мере, в ближайшей перспективе. Тестовые модули интегрированы с кодом, а не находятся в отдельном каталоге, и это важно для нас. Конфигурация очень похожа на этот вопрос и мой ответ , что также может дать полезную информацию.
Проблема, с которой я сталкиваюсь, заключается в том, что проекты используют неявные пакеты пространства имен PEP 420 почти исключительно; то есть, в любом из каталогов пакетов почти нет файлов __init__.py
. Я еще не видел случаев, когда пакеты имели в качестве пакетов пространства имен, но, учитывая, что этот проект объединен с другими проектами, которые также имеют код Python, это может произойти (или уже происходит, и я ' мы просто этого не заметили).
Рассмотрим хранилище, которое выглядит следующим образом. (Для работоспособной его копии, включая тесты, описанные ниже, клон 0cjs/pytest-impl-ns-pkg
от GitHub.) Предполагается, что все тесты ниже находятся в project/thing/thing_test.py
.
repo/
project/
util/
thing.py
thing_test.py
У меня достаточно контроля над конфигурациями тестирования, поэтому я могу убедиться, что sys.path
настроен правильно для правильной работы импорта тестируемого кода. То есть пройдёт следующий тест:
def test_good_import():
import project.util.thing
Однако Pytest определяет имена пакетов из файлов, используя свою обычную систему , предоставляя имена пакетов, которые не являются стандартными для моей конфигурации, и добавляя подкаталоги моего проекта в sys.path
. Поэтому следующие два теста не пройдены:
def test_modulename():
assert 'project.util.thing_test' == __name__
# Result: AssertionError: assert 'project.util.thing_test' == 'thing_test'
def test_bad_import():
''' While we have a `project.util.thing` deep in our hierarchy, we do
not have a top-level `thing` module, so this import should fail.
'''
with raises(ImportError):
import thing
# Result: Failed: DID NOT RAISE <class 'ImportError'>
Как вы можете видеть, хотя thing.py
всегда можно импортировать как project.util.thing
, thing_test.py
это project.util.thing_test
вне Pytest, но при запуске Pytest project/util
добавляется к sys.path
и модуль по имени thing_test
.
Это создает ряд проблем:
- Коллизии пространства имен модуля (например, между
project/util/thing_test.py
и project/otherstuff/thing_test.py
).
- Неправильные операторы импорта не обнаруживаются, поскольку тестируемый код также использует эти непроизводственные пути импорта.
- Относительный импорт может не работать в тестовом коде, поскольку модуль был «перемещен» в иерархии.
- В общем, я довольно нервничаю из-за того, что в тестирование добавляется большое количество дополнительных путей к
sys.path
, которые будут отсутствовать при производстве, так как я вижу в этом большую вероятность ошибок. Но давайте назовем это первым (и на данный момент, я думаю, стандартным) вариантом.
То, что я хотел бы сделать, это сказать Pytest, что он должен определять имена модулей относительно конкретных путей к файловой системе, которые я предоставляю, а не сам решать, какие пути использовать, основываясь на наличии и отсутствии __init__.py
файлы. Однако я не вижу способа сделать это с помощью Pytest. (Не исключено, что я добавлю это в Pytest, но этого также не произойдет в ближайшем будущем, так как я думаю, что мне нужно более глубокое понимание Pytest, прежде чем даже предлагать, как именно это сделать.)
Третий вариант (после того, как вы просто живете с текущей ситуацией и меняете pytest, как указано выше), просто добавляет в проект десятки __init__.py
файлов. Однако, хотя с использованием extend_path
в них будет (я думаю) иметь дело с проблемой пространства имен по сравнению с обычными пакетами в обычном мире Python, я думаю, что это сломает нашу необычную систему выпуска пакетов, объявленных в нескольких проектах. , (То есть, если бы другой проект имел модуль project.util.other
и был объединен для выпуска с нашим проектом, столкновение между их project/util/__init__.py
и нашим project/util/__init__.py
было бы серьезной проблемой.) Исправление этого было бы серьезной проблемой, так как мы пришлось бы, помимо прочего, добавить какой-то способ объявить, что некоторые каталоги, содержащие __init__.py
, на самом деле являются пакетами пространства имен.
Есть ли способы улучшить вышеуказанные опции? Есть ли другие варианты, которые я пропускаю?