Подтвердить что ты не робот

Как насмехаться с базовым классом с помощью макетной библиотеки python

Я пытаюсь использовать mock для написания некоторых модульных тестов в python.

Например, у меня есть следующий класс:

class TCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        self.data = self.request.recv(1024).strip()

И я хочу только протестировать метод handle. Без необходимости ничего говорить о socketserver.BaseRequestHandler. Например, я хочу утверждать, что handle вызывает recv с аргументом 1024. Можно ли это сделать с макетом? То есть заменив базовый класс socketserver.BaseRequestHandler макетом? Или я не согласен с этой идеей?


С ответом ecatmur (спасибо!) я сначала попробовал следующее:

patcher = patch.object(TCPHandler, '__bases__', (Mock,))
with patcher:
    patcher.is_local = True
    handler = TCPHandler()
    handler.handle()

Но теперь handle не называется anylonger, а dir(handler) дает:

['assert_any_call', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'method_calls', 'mock_add_spec', 'mock_calls', 'reset_mock', 'return_value', 'side_effect']

type(handler) дает  <class 'mock.TCPHandler'>

Я понимаю, что исправление базового класса также превращает мой производный класс в макет.


Я попробовал еще одну идею:

mock = MagicMock()
TCPHandler.handle(mock)
#assertions

Однако макет, кажется, не называется.

4b9b3361

Ответ 1

Вы можете сделать это, исправив производный класс __bases__:

def test_derived():
    patcher = mock.patch.object(Derived, '__bases__', (mock.Mock,))
    with patcher:
        patcher.is_local = True
        d = Derived()
        print d.foo()

Хвост is_local необходимо остановить mock.patch от попытки вызова delattr при изменении патча.

Ответ 2

Я думаю, что проблема в том, что вы пытаетесь высмеять фактический код, который хотите проверить. Вместо объектов, вызываемых этим кодом. Если вам интересно узнать, вызывает ли метод дескриптора метод recv на self.request, тогда вытереть метод recv.

def test_tcp_handler_method(self):

    handler = TCPHandler()
    handler.request = Mock()

    handler.handle()

    self.assertTrue(handler.request.recv.called)
    self.assertEqual(handler.request.recv.call_args[0], 1024)

Возможно, вам придется выполнить некоторую дополнительную настройку, чтобы получить обработчик для создания экземпляра, но основная идея должна быть ясной.

Ответ 3

Я не знаю, является ли это лучшим решением, но мне удалось переопределить предыдущий класс с другим родителем, используя type(). Я построил функцию под названием patch_parent(), которая возвращает класс с родительским макетом:

from contextlib import contextmanager

@contextmanager
def patch_parent(class_):
    """
    Mock the bases
    """
    yield type(class_.__name__, (Mock,), dict(class_.__dict__))

После этого вы можете использовать patch_parent следующим образом:

class Bar():
   def method(self, param1, param2...):
       ...

class Foo(Bar):
   pass


>>> with patch_parent(Foo) as MockFoo:
...     f = MockFoo()
...     print f
...     print f.method()
... 
<Foo id='15488016'>
<Foo name='mock.method()' id='15541520'>
>>> s = Foo()
>>> s.method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method() takes exactly 3 arguments (1 given)

Класс MockFoo по-прежнему имеет методы класса Foo, и он не имеет методов, определенных в родительском, поскольку родительский класс теперь является классом Mock.