从python中的模块模拟​​所有类

时间:2018-09-24 10:56:58

标签: python unit-testing mocking python-unittest python-mock

我在测试环境中的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”。我在这里做错了什么?

1 个答案:

答案 0 :(得分:0)

MagicMock实例没有为模块提供与导入机器相同的信息。即使使用spec,在模拟中也没有定义实际的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模块以获得指定的替换: / p>

# 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