必须在导入被测模块前用sys.modules预置Mock对象,否则ImportError会直接中断测试;pytest可选importorskip跳过依赖,但需行为模拟时仍须sys.modules方案。

mock未安装模块时ImportError会直接中断测试
Python单元测试中直接import一个不存在的第三方模块(比如torch或tensorflow),测试运行器会在导入阶段抛出ImportError,根本不会进入setUp或test_*方法——这意味着你连mock的机会都没有。
关键不是“怎么mock”,而是“怎么让导入不失败”。常见错误是试图在setUp里用patch去mock模块,但此时模块早已导入失败。
- 必须在模块被真实导入前,用
sys.modules提前注入一个占位对象 - 不能依赖
unittest.mock.patch装饰器作用于测试函数——它太晚了 - 如果被测代码在
__init__.py或模块顶层就import,那mock必须在import被测模块之前完成
用sys.modules提前注册mock模块
最可靠的方式是手动向sys.modules写入一个空的Mock模块,在真正import发生前“骗过”Python导入系统。
示例:假设你的代码里有from torch import nn,但CI环境没装torch:
立即学习“Python免费学习笔记(深入)”;
import sys from unittest.mock import Mock <h1>在 import your_module 之前执行</h1><p>if 'torch' not in sys.modules: sys.modules['torch'] = Mock() sys.modules['torch.nn'] = Mock() sys.modules['torch.nn.Module'] = Mock</p><p>import your_module # 现在能成功导入了
注意:sys.modules写入必须在任何可能触发import torch的语句之前;如果被测模块是包,还要补全所有子模块路径(如torch.optim、torch.cuda等)。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
用pytest-importorskip跳过依赖检查(仅限pytest)
如果你用的是pytest,且只是想“跳过”某些测试而非模拟行为,pytest.importorskip更轻量、语义更清晰:
import pytest
<p>torch = pytest.importorskip("torch", reason="torch not available")</p><p>def test_torch_specific_feature():
assert hasattr(torch, "Tensor")
但它不适用于需要“假装模块存在并返回可控行为”的场景(比如测试条件分支逻辑)。此时仍需sys.modules方案。
-
importorskip会跳过整个测试函数,不执行任何断言 - 它不会修改
sys.modules,也不会影响其他测试对同一模块的导入尝试 - 适合做“功能可用性标记”,不适合做行为mock
mock后要注意模块属性访问的默认行为
用Mock()塞进sys.modules后,所有属性访问(如torch.nn.Linear)默认返回新的Mock对象,这可能导致意外通过或掩盖bug。
例如:isinstance(model, torch.nn.Module) 默认返回False(因为Mock不是真正的类),但你可能期望它是True。这时要显式配置:
from unittest.mock import Mock <p>torch_mock = Mock() torch_mock.nn.Module = Mock # 让 isinstance(x, torch.nn.Module) 成立 torch_mock.nn.Linear.return_value = Mock() sys.modules['torch'] = torch_mock
- 不要依赖Mock的自动链式返回,尤其涉及
isinstance、issubclass、hasattr时 - 若被测代码调用
torch.__version__,需手动设torch_mock.__version__ = "2.0.0" - mock越粗粒度(如只mock
torch),后续越容易因隐式属性访问出问题
真正麻烦的不是mock本身,而是被测模块的导入时机和深度——有些库会在__getattr__或延迟导入里动态查模块,这种就得结合importlib.util.find_spec或自定义MetaPathFinder,但95%的场景,sys.modules预置+属性微调已足够。

















