在用mock这个库的时候碰到一个坑,即“where to patch”的问题。我尝试把问题重现了一下,简化成了如下的版本:

首先有这样一个安装好的package,目录结构如下:

mypkg
├── __init__.py
├── lib
│   ├── __init__.py
│   ├── another_lib.py
│   └── mylib.py
└── main.py

其中,another_lib.py中代码很简单,就一个函数:

def hello():
    print('hello')

mylib.py中会调用another_lib.py中的那个函数:

from another_lib import hello

def myfunc():
    hello()

main.py中则调用了mylib.py中的函数(两个__init__.py都是空的):

import sys
import os
dirname = os.path.dirname(os.path.normpath(os.path.join(__file__)))
sys.path.append(os.path.join(dirname, "lib"))
from mylib import myfunc

def main():
    myfunc()

现在,在我的目标是想要用mock来替换函数hello

按照正常的思路,我是这么做的:

from mypkg.main import main
from mock import patch

@patch('mypkg.lib.mylib.hello')
def demo(mock_func):
    main()

if __name__ == '__main__':
    demo()

但实际情况是,”hello”还是被打印出来了,即替换失败了。

正确的做法是这样的:

from mypkg.main import main
from mock import patch

@patch('mylib.hello')
def demo(mock_func):
    main()

if __name__ == '__main__':
    demo()

即一开始patch的位置不对,but why?

我们可以看一看所有import的模块,找一找其中名为mylib的模块:

import sys

modules = [sys.modules[v] for v in sys.modules.keys() if 'mylib' in v]
print(modules)
# [<module 'mylib' from '/usr/local/lib/python2.7/site-packages/mypkg/lib/mylib.pyc'>, None, <module 'mypkg.lib.mylib' from '/usr/local/lib/python2.7/site-packages/mypkg/lib/mylib.pyc'>]

发现了没,这样类似的模块竟然有三个!其中有两个还是指向的同一个文件!

造成这种情况的原因其实就是因为main.py中通过修改sys.path来import了mylib.py中的成员,sys.path中添加了路径之后,该路径下的所有模块又重新导入了一遍,又因为相对路径不同,导入的模块名称就变成了mylib,而不是原来的mypkg.lib.mylib。虽然两者指向的是同一个文件,但在内存中却是两个不同的Python模块对象,在main.py中import的是mylib.myfunc,执行时自然调用的是mylib.hello