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

Python类Mock异常:命名空间包可用,常规包失效

关于Python单元测试中Mock配置类的问题与解答

上下文

我正在给应用写单元测试,configuration/connections.py模块里有个通过环境变量定义的配置类:

from typing import Literal

from pydantic_settings import BaseSettings

class StorageConfig(BaseSettings):
    storage_name: Literal["file", "s3"]

这个配置在connections/storage/main.py里被调用:

import fsspec

from configuration.connections import StorageConfig

storage_config = StorageConfig()

fs: fsspec.AbstractFileSystem = fsspec.filesystem(storage_config.storage_name)

因为跑单元测试时没有环境配置,我尝试MockStorageConfig对象:

import pytest
from pydantic import BaseModel

@pytest.fixture(autouse=True)
def local_config(mocker):
    class LocalStorageConfig(BaseModel):
        storage_name: str = "file"

    mocker.patch("configuration.connections.StorageConfig", new=LocalStorageConfig)

问题场景

根据文件夹结构不同,Mock的效果时好时坏:

  • 当connections.storage是命名空间包,测试文件放在connections/storage/tests/test_storage.py,直接patchconfiguration.connections.StorageConfig时Mock正常工作(和文档建议的patch位置不一样)。
  • 当connections.storage是常规包,其他条件和上面一致,Mock失效。
  • 把测试文件移到tests/integration/test_storage.py,其他条件和第二个场景一致,Mock又恢复正常。

疑问解答

1. 为什么常规包和命名空间包会影响Mock效果?

核心差异在于Python的模块缓存机制:

  • 命名空间包:没有__init__.py,导入时Python会把所有同名目录合并成一个命名空间,不会提前缓存connections.storage.main对StorageConfig的引用。当你patchconfiguration.connections.StorageConfig后,connections.storage.main后续的导入会直接使用被patch后的类,Mock生效。
  • 常规包:带有__init__.py,Python会把整个包作为模块缓存。当测试文件和connections.storage同目录时,运行测试会优先加载connections.storage.main,此时StorageConfig已经被导入并缓存到main模块的本地空间里——后续patch原始类的操作,无法覆盖已经缓存的引用,导致Mock失效。

2. 测试文件的位置为什么会影响Mock效果?

这和Python的模块搜索路径(sys.path)以及导入顺序直接相关:

  • 当测试文件在connections/storage/tests/下时,运行测试时当前目录会被加到sys.path最前端。此时导入connections.storage.main时,Python会优先从当前目录的父目录加载connections包,导致main模块提前加载,缓存了原始的StorageConfig引用,后续的patch无法覆盖这个缓存。
  • 当测试文件移到tests/integration/下时,sys.path的优先项是项目根目录,此时测试代码会先执行patch操作,再加载connections.storage.main,main里导入的就是被patch后的StorageConfig,Mock自然生效。

3. 为什么按照文档建议patchconnections.storage.main.StorageConfig时无法生效?

问题出在模块加载时机:connections.storage.main模块里,storage_config = StorageConfig()是在模块加载阶段直接执行的。如果patch操作晚于模块加载,那么storage_config已经是用原始类实例化好的对象了——此时patchconnections.storage.main.StorageConfig这个类,不会改变已经创建完成的storage_config实例,所以看起来Mock失效。

正确的做法是要么确保patch在模块加载前执行,要么直接patchconnections.storage.main.storage_config这个已实例化的对象,而非类本身。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 23:09:58