python - 为什么unittest的 `mock.patch.start`会重新运行启动补丁程序的函数?

标签 python python-unittest patch monkeypatching python-unittest.mock

假设我们有两个文件:

to_patch.py​​

from unittest.mock import patch

def patch_a_function():
    print("Patching!")
    patcher = patch("to_be_patched.function")
    patcher.start()
    print("Done patching!")

to_be_patched.py

from to_patch import patch_a_function

def function():
    pass

patch_a_function()
function()

然后我们运行python -m to_be_patched。这将输出:

Patching!
Patching!
  1. 为什么没有打印Done patching!
  2. 为什么 Patching! 打印两次?

我已将答案缩小到 (2);对 patch.start 的调用似乎再次触发 patch_a_function 。我怀疑这是因为它是在 to_be_patched.py 中导入的,但我不确定为什么该函数本身会第二次运行。同样,我不确定为什么在对 patch_a_function 的任何调用中都没有到达 Done patching! 行。 patcher.start() 不能阻塞,因为程序很好地退出而不是卡在那里......对吗?

编辑:呵呵。看起来没有人可以重现完成修补!没有被打印(这确实是主要困难) - 所以我想这只是我这边的问题

最佳答案

  1. Why isn't Done patching! ever printed?

无法重现。

$ python -m to_be_patched
Patching!
Patching!
Done patching!
Done patching!
  1. Why is Patching! printed twice?

您的模块被导入两次。如果将 print(__name__) 添加到文件 to_be_patched.py 中,就会很清楚:

from to_patch import patch_a_function

print(f"{__name__=}")

def function():
    pass

patch_a_function()
function()  # note: this line doesn't actually do anything, and could be commented out

结果:

$ python -m to_be_patched
__name__='__main__'
Patching!
__name__='to_be_patched'
Patching!
Done patching!
Done patching!

当您使用python -m to_be_patched时,您的模块to_be_patched将被加载为top-level code ,即模块 __name__ 将是 "__main__"

当使用mock.patch时,mock将first import the patch target 。当以像 "to_be_patched.function" 这样的字符串形式给出补丁目标时,模拟将使用 importlib,通过 pkgutil.resolve_name ,找到要修补的正确命名空间。该方法加载__name__"to_be_patched"的目标模块,它不是顶级代码环境。尽管加载的是相同的底层 .py 文件,但由于名称不匹配,sys.modules 中存在缓存未命中:"__main__"!= "to_be_patched"

函数patch_a_function现在具有双重身份,并且存在于模块__main__以及模块to_be_patched中,所以你所看到的每个人都被叫到。通过所描述的双重导入机制,第一个调用触发第二个调用。

关于python - 为什么unittest的 `mock.patch.start`会重新运行启动补丁程序的函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77371281/

相关文章:

Python单元测试: How to assert the existence of a file or folder and print the path on failure?

patch - 使用 xcode 维持 libpd 的纯数据

ruby-on-rails - rails 4 : Force method: :post

c - 如何为 VyOS 内核编写补丁

python - 属性错误: type object 'MyUser' has no attribute 'USERNAME_FIELD'

python - 这有效,但为什么?

python pytest 偶尔会因 OSError : reading from stdin while output is captured 而失败

python - 使用 python 单元测试模拟 os.environ

python - Pyglet 中的比例分辨率

python - 关于python类定义的一个基本问题