DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
晚安云;香港 CN2 ¥15/月
雨云;给项目一台云服务器

pytest 开启 doctest 后找不到同文件 fixture:检查收集节点

pytest 9.1 开发构建曾因 DoctestModule 与普通 Module 分别收集同一文件,使内联 fixture 不可见。正式版采用分别注册,同时要留意高作用域 autouse fixture 的重复执行。

普通 pytest 运行能找到测试文件内定义的 fixture,加上 --doctest-modules 后却报 fixture 'foo_fixture' not found。如果正在使用 pytest 9.1 的开发构建,应检查同一个 Python 文件是否分别由 doctest 与普通测试收集器处理。

明确的缺失回归出现在 9.1.0.dev386 对应的开发提交。不能据此认定正式 9.1.0 或所有后续版都找不到 fixture:正式版采用了分别注册的处理,同时带来另一项需要关注的行为变化。

同一路径可以对应两个收集对象

下面是触发缺失的简单文件结构,fixture 与普通测试在同一个模块:

import pytest

@pytest.fixture
def foo_fixture():
    return 1

def test_foo(foo_fixture):
    assert foo_fixture == 1

开启 doctest 后,该文件可能同时拥有 DoctestModule 和普通 Module。当 fixture 可见性绑定到具体节点对象时,注册给前一个对象的 fixture 不自动成为后一个对象的定义;如果再因“这个模块已经处理过”而跳过第二次注册,普通测试便找不到它。

正式 9.2.0 的收集实现允许对应节点分别登记 fixture。对于上述开发回归,采用项目兼容的正式版本比复制内部收集代码更合适,不要把私有 fixture 注册表当作应用补丁接口。

共享定义放在 conftest,但不随意改变作用域

需要普通测试和 doctest 共同使用的 fixture,可以移到两者都可发现的 conftest.py。例如将上述定义保留为默认函数作用域:

# conftest.py
import pytest

@pytest.fixture
def foo_fixture():
    return 1

测试文件仅保留请求该 fixture 的测试。目录放置决定可发现范围;不要把所有定义一律搬到项目根目录,否则可能改变其他测试的覆盖关系。已有同名 fixture、插件定义与导入副作用也需要一起核对。

pytest 9.1.0 正式行为还明确允许测试模块内的 module、package 或 session 作用域 autouse fixture,在双重收集条件下可能执行两次。若这类初始化必须共享,可评估把定义移到合适的 conftest;但不应为了减少一次初始化,把原本函数隔离的资源升为 session 级,那会改变资源复用与清理时机。

收集成功后还要检查初始化次数

验收要分别保留普通运行与开启 doctest 的运行,核对两类测试是否都被收集、fixture 是否可见、初始化和 teardown 是否符合原设计。找不到 fixture 与初始化两次是不同阶段的问题,不能以消除前一个错误代替后者的检查。

如果同样的报错只随命令行文件顺序改变,或者请求 fixture 的测试不在 conftest 的可见目录内,应按那个入口继续定位,不能仅凭错误文字套用 doctest 的处理。