真实对比from external import fetch和import external两种写法,验证unittest.mock.patch到底该填哪个路径才能真的生效,两种写法给出的答案完全相反。
代码 external.py / service.py / service2.py# external.py def fetch(): return "REAL_DATA_FROM_NETWORK" # service.py —— from ... import 具体名字 from external import fetch def get_report(): return f"report: {fetch()}" # service2.py —— import 整个模块 import external def get_report(): return f"report: {external.fetch()}"
with mock.patch("external.fetch", return_value="FAKE"):
service.get_report()
→ report: REAL_DATA_FROM_NETWORK ← 真实patch没生效,还是走了真实的fetch()
with mock.patch("service.fetch", return_value="FAKE"):
service.get_report()
→ report: FAKE ← 真实patch生效了with mock.patch("external.fetch", return_value="FAKE"):
service2.get_report()
→ report: FAKE ← 真实生效了!跟上面service.py的结论正好相反
with mock.patch("service2.fetch", return_value="FAKE", create=True):
service2.get_report()
→ report: REAL_DATA_FROM_NETWORK ← 真实完全没用——因为service2的命名空间里根本没有fetch这个名字区别在于service2.py写的是import external(拿整个模块对象),调用的时候真实是external.fetch()——这个调用在执行的那一刻,才真的去查external模块的命名空间找fetch这个名字,service2自己的命名空间里从来就没有一个独立的fetch名字(这也是为什么第二个patch要加create=True才不会因为"属性原本不存在"直接报错,即便加了,效果也是patch了一个service2从来不会用到的属性)。所以mock.patch("external.fetch", ...)这次真实改到了point,因为service2查找fetch的路径,真实经过的正是external这个模块对象本身。
每一步都是本文上面用真实的unittest.mock跑出来的原始结果。
unittest.mock