从 python 中的模块中模拟所有 类
mocking all classes from a module in python
我在测试环境的 3 个不同目录中有 test_a.py、a.py 和 b.py。
b.py
class SBD():
def __init__(self):
print("SBD created (In B)")
a.py
import b
from b import *
print("In module A")
def fun1():
a=SBD()
print("SBD created(In A)")
test_a.py
import unittest
import sys
from unittest.mock import Mock,patch,MagicMock
sys.path.append("../abc/")
import b as c
sys.modules['b'] = MagicMock(spec=c)
sys.path.append("../xyz/")
import a
class TestStringMethods(unittest.TestCase):
def test_isupper(self):
a.fun1()
if __name__ == '__main__':
unittest.main()
在实际情况下,b.py 将有多个 类,我想模拟所有这些,所以我尝试模拟具有相同规范的模块 b。但是当我 运行 和 test_a.py 它给我一个错误,说 "SBD" 没有定义。我在这里做错了什么?
A MagicMock
实例不会像模块那样向导入机器提供相同的信息。即使使用 spec
,也没有在 mock 上定义实际的 SDB
属性,因此 from b import *
找不到它。
from *
导入机制尝试两种不同的方法:
- 它尝试访问名称
__all__
;如果已定义,它必须是要导入的名称字符串列表。
- 如果未定义
__all__
,则采用 __dict__
属性的键,过滤掉以下划线开头的名称。
因为您的 b
模块没有定义 __all__
列表,所以取而代之的是 __dict__
键。对于针对模块指定的 MagicMock
实例,__dict__
属性仅包含带有 _
下划线的名称和 mock_calls
属性。 from b import *
仅导入 mock_calls
:
>>> import a as c
>>> module_mock = MagicMock(spec=c)
>>> [n for n in module_mock.__dict__ if n[:1] != '_']
['method_calls']
我强烈建议反对 模拟整个模块;这样做需要您推迟导入 a
,并且 脆弱 。该补丁是永久性的(测试结束时不会自动撤消)并且不支持重复运行测试或 运行 随机顺序测试。
但是如果您必须来完成这项工作,您可以先向模拟添加一个__all__
属性:
sys.modules['b'] = MagicMock(spec=c, __all__=[n for n in c.__dict__ if n[:1] != '_'])
就我个人而言,我会 a) 完全避免使用 from module import *
语法。如果无论如何我都无法阻止它被使用,下一步将是在 导入后将补丁应用到 a
,循环遍历 b
模块以获得指定的替换:
# avoid manipulating sys.path if at all possible. Move that to a PYTHONPATH
# variable or install the modules properly.
import unittest
from unittest import mock
import a
import b
class TestStringMethods(unittest.TestCase):
def setUp(self):
# mock everything `from b import *` would import
b_names = getattr(b, '__all__', None)
if b_names is None:
b_names = [n for n in b.__dict__ if n[:1] != '_']
self.b_mocks = {}
for name in b_names:
orig = getattr(b, name, None)
if orig is None:
continue
self.b_mocks[name] = mock.patch.object(a, name, spec=orig)
self.b_mocks[name].start()
self.addCleanup(self.b_mocks[name].stop)
def test_isupper(self):
a.fun1()
这使 sys.modules['b']
保持不变,并处理与 from *
将加载的完全相同的名称。补丁在测试结束后再次被移除。
以上测试输出:
$ python test_a.py
In module A
SBD created(In A)
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
我在测试环境的 3 个不同目录中有 test_a.py、a.py 和 b.py。
b.py
class SBD():
def __init__(self):
print("SBD created (In B)")
a.py
import b
from b import *
print("In module A")
def fun1():
a=SBD()
print("SBD created(In A)")
test_a.py
import unittest
import sys
from unittest.mock import Mock,patch,MagicMock
sys.path.append("../abc/")
import b as c
sys.modules['b'] = MagicMock(spec=c)
sys.path.append("../xyz/")
import a
class TestStringMethods(unittest.TestCase):
def test_isupper(self):
a.fun1()
if __name__ == '__main__':
unittest.main()
在实际情况下,b.py 将有多个 类,我想模拟所有这些,所以我尝试模拟具有相同规范的模块 b。但是当我 运行 和 test_a.py 它给我一个错误,说 "SBD" 没有定义。我在这里做错了什么?
A MagicMock
实例不会像模块那样向导入机器提供相同的信息。即使使用 spec
,也没有在 mock 上定义实际的 SDB
属性,因此 from b import *
找不到它。
from *
导入机制尝试两种不同的方法:
- 它尝试访问名称
__all__
;如果已定义,它必须是要导入的名称字符串列表。 - 如果未定义
__all__
,则采用__dict__
属性的键,过滤掉以下划线开头的名称。
因为您的 b
模块没有定义 __all__
列表,所以取而代之的是 __dict__
键。对于针对模块指定的 MagicMock
实例,__dict__
属性仅包含带有 _
下划线的名称和 mock_calls
属性。 from b import *
仅导入 mock_calls
:
>>> import a as c
>>> module_mock = MagicMock(spec=c)
>>> [n for n in module_mock.__dict__ if n[:1] != '_']
['method_calls']
我强烈建议反对 模拟整个模块;这样做需要您推迟导入 a
,并且 脆弱 。该补丁是永久性的(测试结束时不会自动撤消)并且不支持重复运行测试或 运行 随机顺序测试。
但是如果您必须来完成这项工作,您可以先向模拟添加一个__all__
属性:
sys.modules['b'] = MagicMock(spec=c, __all__=[n for n in c.__dict__ if n[:1] != '_'])
就我个人而言,我会 a) 完全避免使用 from module import *
语法。如果无论如何我都无法阻止它被使用,下一步将是在 导入后将补丁应用到 a
,循环遍历 b
模块以获得指定的替换:
# avoid manipulating sys.path if at all possible. Move that to a PYTHONPATH
# variable or install the modules properly.
import unittest
from unittest import mock
import a
import b
class TestStringMethods(unittest.TestCase):
def setUp(self):
# mock everything `from b import *` would import
b_names = getattr(b, '__all__', None)
if b_names is None:
b_names = [n for n in b.__dict__ if n[:1] != '_']
self.b_mocks = {}
for name in b_names:
orig = getattr(b, name, None)
if orig is None:
continue
self.b_mocks[name] = mock.patch.object(a, name, spec=orig)
self.b_mocks[name].start()
self.addCleanup(self.b_mocks[name].stop)
def test_isupper(self):
a.fun1()
这使 sys.modules['b']
保持不变,并处理与 from *
将加载的完全相同的名称。补丁在测试结束后再次被移除。
以上测试输出:
$ python test_a.py
In module A
SBD created(In A)
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK