Экземпляр
A 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] != '_'])
Лично я бы а) вообще не использовал синтаксис 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