我正在使用 python 模拟库,我不确定为什么会得到这个结果。为什么只有第二个被嘲笑而不是第一个?最好的方法是什么?
import unittest
from unittest.mock import patch, Mock
import requests
from requests import Session
def my_func():
s = Session()
print('my_func', type(s))
def my_func2():
s = requests.Session()
print('my_func2', type(s))
class Test(unittest.TestCase):
@patch("requests.Session", new=Mock)
def test1(self, *args):
my_func()
@patch("requests.Session", new=Mock)
def test2(self, *args):
my_func2()
输出
my_func <class 'requests.sessions.Session'>
my_func2 <class 'unittest.mock.Mock'>
这是 Python 命名空间的一个棘手的细微差别。 在第一个
my_func()
中,您仅使用了 Session
(使用 from requests import Session
将其导入到模块命名空间后)。 在 my_func2()
中,您使用了 requests.Session
。
@patch('requests.Session')
正在替换 Session
模块中的 requests
,因此
requests.Session
将成为模拟对象。 但它并不能取代每个模块中对
Session
的所有引用——事实上,这在 Python 中很难做到。实际上,在基本层面上,
patch()
所做的只是设置
requests.Session = Mock()
。 但要替换对模块全局命名空间中已有的
Session
的引用,还必须设置
globals()['Session'] = Mock()
。换句话说,您必须在正确的命名空间中修补对象。
如果您有某个模块
foo.py
包含:
from requests import Session
def my_func():
s = Session()
...
然后是一个单独的测试模块test_foo.py
,您想在其中模拟
Session
类,您必须在
foo
的命名空间中对其进行修补,例如:
import foo
@patch('foo.Session', ...)
def test_my_func():
...
patch() 的工作原理是(暂时)将一个名称指向的对象更改为另一个名称所指向的对象。可以有许多名称指向任何单个对象,因此为了使修补工作正常进行,您必须确保修补被测试系统使用的名称。
基本原则是在查找对象的位置进行修补,该位置不一定与定义对象的位置相同。几个示例将有助于阐明这一点。
假设我们有一个要测试的项目,其结构如下:
a.py
-> Defines SomeClass
b.py
-> from a import SomeClass
-> some_function instantiates SomeClass
现在我们想要测试 some_function 但我们想使用 patch() 模拟 SomeClass。问题是,当我们导入模块 b 时(我们必须这样做),它会从模块 a 导入 SomeClass。如果我们使用 patch() 来模拟 a.SomeClass 那么它不会对我们的测试产生影响;模块 b 已经引用了真正的 SomeClass,看起来我们的修补没有效果。关键是在使用 SomeClass 的地方(或者查找它的地方)修补它。在这种情况下,some_function 实际上会在模块 b 中查找 SomeClass,我们已将其导入到该模块中。修补程序应如下所示:
@patch('b.SomeClass')
但是,请考虑另一种情况,其中不是从 import SomeClass 模块 b 而是导入 a 并且 some_function 使用 a.SomeClass。这两种导入形式都很常见。在这种情况下,我们想要修补的类正在模块中查找,因此我们必须修补 a.SomeClass :
@patch('a.SomeClass')