You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pytest插件运行时部分钩子函数未触发问题求助

问题:Pytest插件仅session和log类钩子触发,测试执行相关钩子无输出

开发Pytest插件监听测试运行,实现了多个钩子函数,但运行后仅pytest_sessionstart/pytest_sessionfinish、pytest_runtest_logstart/pytest_runtest_logfinish有输出,pytest_runtest_setup、pytest_runtest_call等钩子完全没触发。尝试改为hookwrapper模式、静态方法均无效。

环境与代码

插件代码

"""
    to test scripted pytest control
"""
import sys

import pytest


class MyPytestPlugin:
    """
    Hooks to get info from pytest
    """

    def __init__(self) -> None:
        pass

    # pylint: disable=unused-argument
    def pytest_collection_modifyitems(self, config, items):
        print("collection done.")

    def pytest_sessionstart(self, session):
        print("*" * 10)
        print("session started", session)
        print("*" * 10)

    def pytest_sessionfinish(self, session, exitstatus):
        print("-" * 10)
        print("session finished", session)

    def pytest_runtest_logstart(self, nodeid, location):
        print("<<<<<< log start >>>>>>>", nodeid)

    def pytest_runtest_logfinish(self, nodeid, location):
        print("<<<<<< log finish >>>>>>>", nodeid)

    def pytest_runtest_protocol(self, item, nextitem):
        print("<<<<<< protocol >>>>>>>")

    def pytest_collectreport(self, report):
        print("<<<<<< collect report >>>>>>>")

    def pytest_report_teststatus(self, report):
        print("<<<<<< test status >>>>>>>")

    def pytest_runtest_setup(self, item):
        print("<<<<<< setup >>>>>>>")

    def pytest_runtest_call(self, item):
        print("<<<<<< runtest call >>>>>>>")

    def pytest_runtest_teardown(self, item):
        print("<<<<<< runtest teardown >>>>>>>")


my_plugin = MyPytestPlugin()
sys.exit(
    pytest.main(
        ["-s", "sample_test.py"],
        plugins=[my_plugin],
    )
)

测试文件sample_test.py

import pytest


@pytest.mark.parametrize(
    "a,b,expected",
    [
        pytest.param(1, 1, 2, id="t1"),
        pytest.param(2, 2, 4, id="t2"),
        pytest.param(2, 1, 3, id="t3"),
        pytest.param(2, 3, 5, id="t4"),
        pytest.param(3, 4, 7, id="t5"),
    ],
)
def test_addon(a, b, expected):
    print(f"{a}, {b}, {expected}")
    assert a + b == expected

运行输出(关键片段)

**********
session started <Session xxxxxxxx exitstatus=<ExitCode.OK: 0> testsfailed=0 testscollected=0>
**********
[gw0] Python 3.9.6 (default, Aug 17 2021, 02:38:04)  -- [GCC 8.3.0]
[gw1] Python 3.9.6 (default, Aug 17 2021, 02:38:04)  -- [GCC 8.3.0]
[gw2] Python 3.9.6 (default, Aug 17 2021, 02:38:04)  -- [GCC 8.3.0]
gw0 [5] / gw1 [5] / gw2 [5]
scheduling tests via LoadScheduling
<<<<<< log start >>>>>>> sample_test.py::test_addon[t1]
# ... 仅log和session相关输出,无setup/call等日志

根因分析

问题出在xdist并行测试插件的工作机制:

  • 主进程仅负责测试用例收集、任务分发、session生命周期管理,不会执行具体测试
  • 实际测试用例的执行(包括setup/call/teardown等阶段)都在worker进程(即输出中的gw0/gw1/gw2)中完成
  • 当前插件仅在主进程中注册,worker进程启动时没有加载这个插件,因此测试执行相关的钩子无法在worker中触发;而log类钩子是主进程负责接收worker的日志事件,所以能触发。

解决方案

方案1:临时禁用xdist(调试用)

如果不需要并行测试,直接在运行时禁用xdist插件,确保所有钩子在单进程中执行:
修改pytest.main的参数,添加-p no:xdist:

sys.exit(
    pytest.main(
        ["-s", "sample_test.py", "-p", "no:xdist"],
        plugins=[my_plugin],
    )
)

运行后所有钩子都会正常触发。

方案2:让worker进程加载插件(保留并行)

要在并行模式下触发所有钩子,必须让每个worker进程都加载你的插件,有几种实现方式:

方式A:将插件代码放到conftest.py

把MyPytestPlugin的定义放到当前目录的conftest.py中,pytest(包括worker进程)会自动加载conftest中的插件:

# conftest.py
import pytest


class MyPytestPlugin:
    # 钩子函数和之前完全一致
    def pytest_sessionstart(self, session):
        print("*" * 10)
        print("session started", session)
        print("*" * 10)
    
    def pytest_runtest_setup(self, item):
        print(f"<<<<<< setup >>>>>>> [{item.nodeid}]")
    
    # ... 其他钩子函数


def pytest_configure(config):
    config.pluginmanager.register(MyPytestPlugin())

然后修改原运行脚本,去掉手动传入plugins的参数:

# sample_test_runner.py
import sys
import pytest

sys.exit(
    pytest.main(
        ["-s", "sample_test.py"],
    )
)

这样worker进程启动时会加载conftest中的插件,所有钩子都会在对应的进程中触发(注意worker的输出会带有[gw0]等前缀)。

方式B:将插件打包为可安装包

把插件做成标准的pytest插件包(符合pytest插件规范),安装后worker进程会自动发现并加载。

方式C:通过配置文件指定插件

在tox.ini或pytest.ini中添加:

[pytest]
pytest_plugins = your_plugin_module_name

确保你的插件模块能被Python导入,worker进程启动时会读取配置并加载插件。

验证

使用方案2的方式A后,运行输出会包含worker进程中的钩子日志:

[gw0] <<<<<< setup >>>>>>> [sample_test.py::test_addon[t2]]
[gw0] <<<<<< runtest call >>>>>>>
[gw0] 2, 2, 4
[gw0] <<<<<< runtest teardown >>>>>>>

内容的提问来源于stack exchange,提问作者Reza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 19:50:19