высмеивать все классы из модуля в Python - PullRequest
0 голосов
/ 24 сентября 2018

У меня есть test_a.py, a.py и b.py в 3 разных каталогах в моей тестовой среде.

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 голосов
/ 24 сентября 2018
Экземпляр

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
...