Pytest插件修改xdist的--dist选项时钩子执行顺序异常问题
我尝试为xdist的--dist标志添加新选项,在plugin.py文件中编写了如下代码:
@pytest.hookimpl def pytest_addoption(parser: pytest.Parser) -> None: # Modify the existing xdist 'dist' option to add our new scheduling mode group = parser.getgroup("xdist", "distributed and subprocess testing") distopt = next(filter(lambda opt: opt.dest == "dist", group.options), None) distopt._attrs["choices"].append("perdevice")
问题在于我需要xdist先完成选项添加,才能对其进行修改。根据Pytest文档,插件钩子按LIFO顺序执行——我认为新插件最后注册,应该最先执行。另外文档提到可通过pytest.hookimpl(tryfirst=True)调整执行顺序,但这完全无效。查看xdist源码,其未启用tryfirst选项,我不明白为何无效,甚至添加trylast也没用。我使用的是pytest-xdist 3.8.0,请问这是为什么?
1. 钩子执行顺序的核心问题
Pytest的钩子执行顺序并非完全遵循文档描述的LIFO逻辑,官方插件和自定义插件存在加载优先级差异。xdist作为官方维护的核心插件,它的pytest_addoption钩子会在你的自定义插件之前执行——哪怕你添加了tryfirst=True也无法改变这个顺序,因为Pytest内部会优先处理内置/官方插件的初始化流程,这个优先级是硬编码的,不受tryfirst参数调整。
2. 直接修改私有属性的隐患
你直接操作distopt._attrs["choices"]属于访问xdist的内部私有实现,在3.8.0版本中,xdist可能会在pytest_addoption钩子执行完成后,重新对选项的choices做固化处理,导致你添加的选项被覆盖。而且这种方式完全依赖xdist的内部结构,版本更新后极易失效。
3. 正确的扩展方式
与其修改现有选项,不如使用xdist原生支持的扩展机制:
- 自定义调度类:继承xdist的
LoadScheduling基类,实现自己的perdevice调度逻辑,再在pytest_configure钩子中替换默认调度器:
from xdist.scheduling import LoadScheduling from xdist.workermanage import WorkerManager class PerDeviceScheduling(LoadScheduling): def __init__(self, config, log): super().__init__(config, log) # 在这里实现按设备分配测试用例的逻辑 def pytest_configure(config): if config.getoption("dist") == "perdevice": WorkerManager.scheduling_class = PerDeviceScheduling
- 安全添加选项choices:如果一定要给
--dist增加选项,可以在pytest_configure钩子中修改(该钩子会在所有pytest_addoption执行完成后触发):
@pytest.hookimpl def pytest_configure(config): parser = config.pluginmanager.getplugin("_parser") group = parser.getgroup("xdist") distopt = next(opt for opt in group.options if opt.dest == "dist") # 确保choices为可变列表后再添加新选项 if "perdevice" not in distopt.choices: distopt.choices = list(distopt.choices) + ["perdevice"]
这种方式能确保修改在xdist选项初始化完成后生效,同时避免依赖内部私有属性。
内容的提问来源于stack exchange,提问作者frimann

