DEBUG.WIKI
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →

pytest 跑完测试,unittest 模块 cleanup 却没有执行

pytest 的模块 xunit 入口并未完整消费 unittest 模块清理栈。把资源交给自己的 yield fixture,避免重复持有或盲目清空全局栈。

测试都跑完了,unittest.addModuleCleanup() 注册的释放函数却没执行;enterModuleContext() 进入的上下文也没有按预期退出。如果实际运行器是 pytest,先检查资源清理由谁负责,不能仅凭 API 属于 unittest 就假设两种运行器的模块生命周期完全相同。

pytest 9.1.1 与 8.3.5 有这类明确记录。正式 9.1.1 对模块级 xunit 入口主要调用 setUpModule、tearDownModule,没有同步执行 unittest 的模块清理栈。标准库 unittest 运行器则会在相应阶段调用 doModuleCleanups(),两条流程不同。

pytest 测试让 fixture 持有自己的资源

项目主要由 pytest 执行时,可以把资源建立与释放放进同一个模块作用域的 yield fixture。下面仅用列表演示生命周期,不连接外部服务:

import pytest

@pytest.fixture(scope="module")
def items():
    values = ["ready"]
    try:
        yield values
    finally:
        values.clear()

def test_uses_items(items):
    assert items == ["ready"]

实际资源要在取得成功后进入对应的清理保护范围。一个 fixture 里顺序取得多个独立资源时,第二次取得失败也应释放第一个;可以拆开 fixture 或使用 ExitStack,不能只在最后写一个 yield 就推定所有 setup 失败都能清理。

从导入时的 enterModuleContext() 迁移时,要先移除原来的重复资源建立,让 fixture 真正成为唯一所有者。否则新 fixture 释放的是新对象,模块导入时留下的旧对象仍可能没有关闭。

不要在每个模块末尾盲目清空全局清理栈

unittest.doModuleCleanups() 弹出的是 unittest 注册的模块清理栈,不是按当前测试文件名字筛选的一份列表。多个模块在收集或导入时已注册清理函数,随手在某个 pytest 终结器里调用它,可能提前结束其他模块仍需要的资源。

需要继续由 unittest 和 pytest 共用同一套测试时,应先设计两种运行器下相同的资源所有权与释放边界。unittest.TestCase 方法也不能直接照抄普通 pytest 测试的 fixture 参数注入;可按已有整合方式使用 autouse fixture,但不要只改一个函数签名就期待两边兼容。

这与SkipTest 在 pytest 9.0.0 被误判为失败是不同的整合问题。升级到修复跳过分类的版本,不会自动补上模块清理栈调用。

迁移前保留运行入口与资源登记位置。复查正常执行、测试失败、setup 中途失败和模块未被选中时的建立与释放次数。回退迁移要恢复同一套登记和所有权安排,不能让旧 cleanup 与新 fixture 同时释放同一个真实资源。