pytest8.x版本 中文使用文档-------25.解释:pytest导入机制和sys.path/PYTHONPATH

目录

导入模式

prepend 和 append 导入模式场景

包内的测试模块/conftest.py文件

独立的测试模块/conftest.py文件

调用 pytest 与使用 python -m pytest


导入模式

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

在Python中导入文件是一个重要的过程,因此可以通过--import-mode命令行标志来控制导入过程的某些方面,该标志可以取以下值:

prepend(默认值):如果每个模块所在的目录路径尚未在sys.path中,则将其插入到sys.path的开头,然后使用importlib.import_module函数进行导入。

强烈建议通过将__init__.py文件添加到包含测试的目录中,将测试模块组织为包。这样做将使测试成为正式的Python包的一部分,允许pytest解析它们的全名(例如,对于tests.core包内的test_core.py文件,其名称为tests.core.test_core)。

如果测试目录树没有按包的形式组织,那么每个测试文件都需要有一个与其他测试文件不同的唯一名称,否则如果pytest找到两个同名测试,将会引发错误。

这是从Python 2仍受支持的时代沿用至今的经典机制。

append: 如果某个包含模块的目录尚未在sys.path中,则将其添加到sys.path的末尾,并使用importlib.import_module导入该模块。

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

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

当使用--import-mode=append时,测试将针对pkg_under_test的已安装版本运行,而如果使用prepend,则会选择本地版本。这种混淆是我们提倡使用src布局的原因之一。

prepend相同,当测试目录树未按包进行组织时,要求测试模块名称必须是唯一的,因为模块在导入后会被放入sys.modules中。

importlib模式:此模式使用importlib提供的更精细的控制机制来导入测试模块,而无需更改sys.path

此模式的优点:

  • pytest完全不会更改sys.path
  • 测试模块名称不需要是唯一的——pytest会根据根目录自动生成一个唯一的名称。

缺点:

  • 测试模块之间不能相互导入。
  • 测试目录中的辅助测试模块(例如,包含与测试相关的函数/类的tests.helpers模块)不可导入。在这种情况下,建议将与应用程序/库代码一起放置测试辅助模块,例如app.testing.helpers
  • 重要提示:这里的“测试辅助模块”指的是被其他测试直接导入的函数/类;这不包括夹具(fixtures),夹具应该放置在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会根据模块相对于根目录的位置为其派生一个唯一名称,并将该模块添加到sys.modules中。

例如,tests/core/test_models.py最终会被导入为模块tests.core.test_models

此功能在版本6.0中添加。

注意

最初,我们打算在未来的版本中使importlib成为默认选项,但现在很明显,它也有自己的一系列缺点,因此在可预见的未来,默认选项将仍然是prepend

注意

默认情况下,pytest不会自动尝试解析命名空间包,但可以通过consider_namespace_packages配置变量来改变这一行为。

另请参阅

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 文件的文件夹,以确定包的根目录(在此例中为 foo/)。为了加载模块,它将在 sys.path 的前面插入 root/(如果它还不在那里的话),以便将 test_foo.py 加载为模块 foo.bar.tests.test_foo

相同的逻辑也适用于 conftest.py 文件:它将被导入为 foo.conftest 模块。

当测试模块位于包中时,保留完整的包名称非常重要,这可以避免问题并允许测试模块具有重复的名称。这在《Python 测试发现约定》中也有详细讨论Conventions for Python test discovery

独立的测试模块/conftest.py文件

考虑以下文件和目录布局:

root/  
|- foo/  
   |- conftest.py  
   |- bar/  
      |- tests/  
         |- test_foo.py

当执行:

pytest root/

pytest 将找到 foo/bar/tests/test_foo.py 并意识到它不是一个包的一部分,因为同一文件夹中没有 __init__.py 文件。然后,它将在 sys.path 中添加 root/foo/bar/tests 以便将 test_foo.py 导入为模块 test_foo。对于 conftest.py 文件,也执行相同的操作,通过在 sys.path 中添加 root/foo 来将其导入为 conftest

因此,这种布局不能有同名的测试模块,因为它们都将被导入到全局导入命名空间中。

这在《Python 测试发现约定》中也有详细讨论。

调用 pytest 与使用 python -m pytest

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

更多信息,请参考“通过 python -m pytest 调用 pytest”的相关文档Calling pytest through python -m pytest

  • 28
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值