Pytest导入机制和 sys.path/PYTHONPATH

导入模式

pytest作为测试框架需要导入测试模块和 conftest.py 执行文件。

在Python中导入文件(至少直到最近)是一个非常重要的过程,经常需要更改 sys.path . 导入过程的某些方面可以通过 --import-mode 命令行标志,可以假定这些值:

  • prepend (default): the directory path containing each module will be inserted into the beginning of sys.path if not already there, and then imported with the __import__ 内置的。

    这要求当测试目录树不在包中排列时,测试模块名称必须是唯一的,因为模块将放入 sys.modules 导入后。

    这是经典的机制,可以追溯到Python2仍然受支持的时候。

  • append :包含每个模块的目录追加到 sys.path 如果不是已经在那里,并且进口 __import__ .

    这样可以更好地针对已安装的包版本运行测试模块,即使被测试的包具有相同的导入根。例如:

    testing/__init__.py
    testing/test_pkg_under_test.py
    pkg_under_test/
    

    测试将针对已安装的版本运行 pkg_under_test 什么时候? --import-mode=append 使用while with prepend 他们会选择本地版本。这种混乱是我们提倡使用的原因 src 布局。

    等同于 prepend ,要求测试模块名称在测试目录树未排列在包中时是唯一的,因为这些模块将放入 sys.modules 导入后。

  • importlib: new in pytest-6.0, this mode uses importlib 导入测试模块。这样就可以完全控制导入过程,而且不需要更改 sys.pathsys.modules 完全。

    由于这个原因,这根本不要求测试模块名称是唯一的,但也使得测试模块彼此不可导入。对于不在Python包中的测试,这在以前的模式中是可能的,因为更改的副作用 sys.pathsys.modules 上面提到过。有此要求的用户应该将他们的测试转换成适当的包。

    我们打算 importlib 未来版本中的默认值。

prependappend 导入模式方案

以下是使用时的场景列表 prependappend 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 文件以查找包 root (在这种情况下) 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/testssys.path 为了进口 test_foo.py 作为 模块 test_foo . 同样的情况也发生在 conftest.py 添加文件 root/foosys.path 将其导入 conftest .

因此,此布局不能具有相同名称的测试模块,因为它们都将在全局导入命名空间中导入。

这也将在中详细讨论 Python测试发现的约定 .

调用 pytest 对战 python -m pytest

使用运行pytest pytest [...] 而不是 python -m pytest [...] 产生几乎相同的行为,除了后者将当前目录添加到 sys.path ,这是标准的 python 行为。

也见 通过调用pytest python -m pytest .