Python 2.7中如何Mock别名导入的库函数并自动恢复?
我懂你现在的困扰:用别名导入了一个长名字的库,想在测试代码里Mock它的函数,而且只让Mock效果局限在测试范围内,测试结束后自动恢复原函数,不想手动做备份和还原的操作。之前试了直接赋值Mock和用patch装饰器都踩坑了,对吧?
先给你说清楚之前@patch(ml.helloworld, MagicMock(...))报错的原因:patch装饰器的第一个参数需要传入字符串形式的模块函数完整路径,而不是直接传ml.helloworld这个函数对象。你直接传函数对象的话,patch内部会尝试对它做字符串拆分操作,自然就抛出AttributeError: 'function' object has no attribute 'rsplit'的错误了。
下面给你两种靠谱的解决办法,都能实现Mock仅在测试内生效、自动恢复原函数的需求:
方法一:使用patch装饰器(正确传参版)
你需要把原模块的完整函数路径以字符串形式传给patch,而不是用别名的对象。比如你的原库是mylibrarywithlongname,要Mock的函数是helloworld,那路径就是"mylibrarywithlongname.helloworld"。
完整代码示例
库文件 mylibrarywithlongname.py
def helloworld(): return "hello world" def helloworld_helper(): return helloworld()
主程序代码
import mylibrarywithlongname as ml from mock import MagicMock, patch @patch("mylibrarywithlongname.helloworld", return_value="oops") def test(mock_helloworld): print(ml.helloworld_helper()) print(ml.helloworld_helper()) test() print(ml.helloworld_helper())
运行结果
oops oops hello world
方法二:使用patch上下文管理器
如果不想用装饰器,用with语句包裹测试逻辑也可以,同样要传字符串形式的原函数路径,退出with块后patch会自动恢复原函数:
import mylibrarywithlongname as ml from mock import MagicMock, patch def test(): with patch("mylibrarywithlongname.helloworld", return_value="oops"): print(ml.helloworld_helper()) print(ml.helloworld_helper()) test() print(ml.helloworld_helper())
运行结果和上面完全一致,最后一行会输出原始的hello world。
原理说明
patch是通过字符串路径找到原模块里的目标函数进行替换的,不管你在主程序里用什么别名(比如ml)导入这个库,只要原模块的路径正确,patch就能准确定位并替换函数。当装饰器的测试函数执行完毕,或者上下文管理器的with块结束,patch会自动把原函数恢复回去,完全不用你手动做备份和还原操作。
内容的提问来源于stack exchange,提问作者576i

