python3 模拟不适用于所有路径
python3 mock doesn't work for all paths
生产文件 (production_file.py) 是:
class MyError(Exception):
pass
class MyClass:
def __init__(self):
self.value = None
def set_value(self, value):
self.value = value
def foo(self):
raise RuntimeError("error!")
class Caller:
def bar(self, smth):
obj = MyClass()
obj.set_value(smth)
try:
obj.foo()
except MyError:
pass
obj.set_value("str2")
obj.foo()
测试文件(test.py):
import unittest
from unittest.mock import patch
from unittest.mock import call
from production_file import MyClass, Caller
class MyTest(unittest.TestCase):
def test_caller(self):
with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
my_class_mock_obj = MyClassMock.return_value
my_class_mock_obj.foo.side_effect = [MyError("msg"), "text"]
caller = Caller()
caller.bar("str1")
calls = [call("str1"), call("str2")]
my_class_mock_obj.set_value.assert_has_calls(calls)
if __name__ == '__main__':
unittest.main()
以上方法有效。但是,如果我将生产 类 (MyError, MyClass, Caller) 移动到测试文件中,并将补丁更新为:
with patch('test.MyClass', autospec=MyClass) as MyClassMock:
那么实例方法"foo"就不再被mock了。
有人知道这是为什么吗?
我在一些更复杂的代码中也遇到过类似的问题,其中生产代码在 my_package/src/production_file.py 中,而测试代码在 my_package/tests/test_file.py 中。 Python 没有产生路径错误,路径是正确的,但模拟仍然不起作用。
如果你是 运行 test.py
和 __main__
那么它不是 test.MyClass
而是 __main__.MyClass
,或者在这两种情况下 __name__+".MyClass"
.
通过添加打印语句,我能够确定使用的 class 和修补的 class 是不同的:
class Caller:
def bar(self, smth):
print(MyClass) #lets see what we are actually making an instance of...
obj = MyClass()
...
当补丁应用到正在使用的 class 时,您会看到类似这样的内容:
<MagicMock name='MyClass' spec='MyClass' id='4387629656'>
但是当 class 移入 test.py
时,您会看到如下内容:
<class '__main__.MyClass'>
表示:
MyClass
没有应用补丁(至少是用于测试的补丁。)
- 需要打补丁的class名称是
__main__.MyClass
但是,您的 "more... complicated situation" 很可能因为这样的设置而无法正常工作:
from production_file import MyClass
class MyError(Exception):
pass
class Caller:
def bar(self, smth):
print(MyClass)
obj = MyClass()
...
class MyTest(unittest.TestCase):
def test_caller(self):
with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
...
在这种情况下,正在修补 production_file.MyClass
并且正在从 production_file
导入 MyClass
,因此正在修补正确的 class 但输出仍然是:
<class 'production_file.MyClass'>
这是因为Class是直接导入到本地命名空间的,所以当补丁应用到production_file
本地命名空间仍然不受影响,我们可以检查补丁实际上是适用于:
...
def bar(self, smth):
print(MyClass)
from production_file import MyClass as pf_MyClass
print(pf_MyClass)
...
#output:
<class 'production_file.MyClass'>
<MagicMock name='MyClass' spec='MyClass' id='4387847136'>
如果是这种情况,您只需要导入模块,而不是直接导入 class。然后一旦应用了补丁,您就可以直接从文件中使用它:
import production_file
...
class Caller:
def bar(self, smth):
print(production_file.MyClass)
obj = production_file.MyClass()
...
class MyTest(unittest.TestCase):
def test_caller(self):
with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
...
生产文件 (production_file.py) 是:
class MyError(Exception):
pass
class MyClass:
def __init__(self):
self.value = None
def set_value(self, value):
self.value = value
def foo(self):
raise RuntimeError("error!")
class Caller:
def bar(self, smth):
obj = MyClass()
obj.set_value(smth)
try:
obj.foo()
except MyError:
pass
obj.set_value("str2")
obj.foo()
测试文件(test.py):
import unittest
from unittest.mock import patch
from unittest.mock import call
from production_file import MyClass, Caller
class MyTest(unittest.TestCase):
def test_caller(self):
with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
my_class_mock_obj = MyClassMock.return_value
my_class_mock_obj.foo.side_effect = [MyError("msg"), "text"]
caller = Caller()
caller.bar("str1")
calls = [call("str1"), call("str2")]
my_class_mock_obj.set_value.assert_has_calls(calls)
if __name__ == '__main__':
unittest.main()
以上方法有效。但是,如果我将生产 类 (MyError, MyClass, Caller) 移动到测试文件中,并将补丁更新为:
with patch('test.MyClass', autospec=MyClass) as MyClassMock:
那么实例方法"foo"就不再被mock了。
有人知道这是为什么吗?
我在一些更复杂的代码中也遇到过类似的问题,其中生产代码在 my_package/src/production_file.py 中,而测试代码在 my_package/tests/test_file.py 中。 Python 没有产生路径错误,路径是正确的,但模拟仍然不起作用。
如果你是 运行 test.py
和 __main__
那么它不是 test.MyClass
而是 __main__.MyClass
,或者在这两种情况下 __name__+".MyClass"
.
通过添加打印语句,我能够确定使用的 class 和修补的 class 是不同的:
class Caller:
def bar(self, smth):
print(MyClass) #lets see what we are actually making an instance of...
obj = MyClass()
...
当补丁应用到正在使用的 class 时,您会看到类似这样的内容:
<MagicMock name='MyClass' spec='MyClass' id='4387629656'>
但是当 class 移入 test.py
时,您会看到如下内容:
<class '__main__.MyClass'>
表示:
MyClass
没有应用补丁(至少是用于测试的补丁。)- 需要打补丁的class名称是
__main__.MyClass
但是,您的 "more... complicated situation" 很可能因为这样的设置而无法正常工作:
from production_file import MyClass
class MyError(Exception):
pass
class Caller:
def bar(self, smth):
print(MyClass)
obj = MyClass()
...
class MyTest(unittest.TestCase):
def test_caller(self):
with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
...
在这种情况下,正在修补 production_file.MyClass
并且正在从 production_file
导入 MyClass
,因此正在修补正确的 class 但输出仍然是:
<class 'production_file.MyClass'>
这是因为Class是直接导入到本地命名空间的,所以当补丁应用到production_file
本地命名空间仍然不受影响,我们可以检查补丁实际上是适用于:
...
def bar(self, smth):
print(MyClass)
from production_file import MyClass as pf_MyClass
print(pf_MyClass)
...
#output:
<class 'production_file.MyClass'>
<MagicMock name='MyClass' spec='MyClass' id='4387847136'>
如果是这种情况,您只需要导入模块,而不是直接导入 class。然后一旦应用了补丁,您就可以直接从文件中使用它:
import production_file
...
class Caller:
def bar(self, smth):
print(production_file.MyClass)
obj = production_file.MyClass()
...
class MyTest(unittest.TestCase):
def test_caller(self):
with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
...