Python按名称导入模块方法时patch mock失效问题咨询
mock
time.sleep() 失效问题解决方案 问题复现
尝试mock time.sleep()时出现三类异常表现:
- 写法1:mock不生效,
sleep()仍执行真实延时
import unittest from unittest.mock import patch from time import sleep class TestTime(unittest.TestCase): @patch('time.sleep', return_value=None) def test_time(self, mock_time): sleep(10) # 真实等待10秒 if __name__ == '__main__': unittest.main()
- 写法2:mock正常生效,无真实延时
import unittest from unittest.mock import patch import time class TestTime(unittest.TestCase): @patch('time.sleep', return_value=None) def test_time(self, mock_time): time.sleep(10) # 瞬间执行完成 if __name__ == '__main__': unittest.main()
- 写法3:直接抛出
TypeError: Need a valid target to patch. You supplied: 'sleep'错误
import unittest from unittest.mock import patch from time import sleep class TestTime(unittest.TestCase): @patch('sleep', return_value=None) # 触发类型错误 def test_time(self, mock_time): sleep(10) if __name__ == '__main__': unittest.main()
运行环境:
- Python版本:3.8.12
- 操作系统:Windows 10 21H1 (19043.1766)
核心诉求:无需将所有模块内的sleep()调用修改为time.sleep()形式,实现正确mock。
失效根因
表现差异由Python导入机制和unittest.mock.patch的工作逻辑共同决定:
- 执行
from time import sleep时,会将time模块下的sleep函数引用直接拷贝到当前模块的命名空间,当前模块的sleep与time.sleep是两个独立名称,初始指向同一个函数对象。 @patch('time.sleep')仅会替换time模块命名空间下的sleep绑定,不会修改其他模块已经完成导入的sleep引用,因此写法1调用当前模块下的sleep时,仍然会执行真实的延时逻辑。patch要求传入的替换目标必须符合包路径.模块名.属性名的格式,直接传入sleep缺少模块上下文,无法定位待替换对象,因此写法3直接抛出参数错误。
可行方案
不需要修改所有调用为time.sleep(),两种方案可直接生效:
方案1:patch对应模块命名空间下的sleep引用
哪个模块里直接用了from time import sleep并调用sleep(),就patch哪个模块路径下的sleep属性即可。
比如测试文件本身名为test_sleep.py,直接patch当前模块下的sleep:
import unittest from unittest.mock import patch from time import sleep class TestTime(unittest.TestCase): # 替换当前模块命名空间下的sleep引用,而非time模块下的 @patch('test_sleep.sleep', return_value=None) def test_time(self, mock_time): sleep(10) # 瞬间执行,无真实延时 mock_time.assert_called_once_with(10) if __name__ == '__main__': unittest.main()
如果是业务模块(例如路径为src/utils.py)中写了from time import sleep并调用,测试时直接patchsrc.utils.sleep即可,不需要修改业务代码的调用形式。
方案2:提前全局patch time.sleep
如果需要跨所有模块统一mock time.sleep,不想逐个模块配置patch路径,可以在测试入口最顶部、所有业务/测试模块导入time.sleep之前,提前启动patch替换time.sleep,后续所有模块不管是import time调用time.sleep,还是from time import sleep导入到自身命名空间,拿到的都是mock对象:
# 测试入口文件最开头执行patch逻辑 import unittest from unittest.mock import patch import time # 提前替换time模块下的sleep sleep_patcher = patch('time.sleep', return_value=None) sleep_patcher.start() # 所有导入逻辑放在patch启动之后 from time import sleep class TestTime(unittest.TestCase): def test_time(self): sleep(10) # 无真实延时 time.sleep(5) # 同样无真实延时 if __name__ == '__main__': unittest.main() # 测试执行完成后可按需恢复真实sleep sleep_patcher.stop()
注意:该方案必须保证patch启动逻辑在所有
from time import sleep导入语句之前执行,否则提前完成导入的模块仍然会持有真实sleep的引用,导致mock失效。
内容的提问来源于stack exchange,提问作者Oilyraincloud
相关产品推荐
相关产品推荐

