Как вы имитируете исправление класса Python и получаете новый объект Mock для каждого экземпляра?

Хорошо,
я знаю, что это упоминается в руководстве, и, вероятно, должен делать с side_effect и/или return_value, но простой прямой пример мне очень поможет.

У меня есть:

class ClassToPatch():
   def __init__(self, *args):
       _do_some_init_stuff()

   def some_func():
       _do_stuff()


class UUT():
    def __init__(self, *args)
       resource_1 = ClassToPatch()
       resource_2 = ClassToPatch()

Теперь я хочу провести модульное тестирование класса UUT и смоделировать класс ClassToPatch. Зная, что класс UUT создаст ровно два объекта ClassToPatch, я хочу, чтобы фреймворк Mock возвращал новый объект Mock для каждого экземпляра, чтобы позже я мог утверждать вызовы для каждого отдельно.

Как мне добиться этого, используя декоратор @patch в тестовом примере? А именно, как исправить следующий пример кода?

class TestCase1(unittest.TestCase):

    @patch('classToPatch.ClassToPatch',autospec=True)
    def test_1(self,mock1,mock2):
        _assert_stuff()

person bavaza    schedule 25.05.2012    source источник


Ответы (2)


Вот быстрый и грязный пример, чтобы вы начали:

import mock
import unittest

class ClassToPatch():
   def __init__(self, *args):
       pass

   def some_func(self):
       return id(self)

class UUT():
    def __init__(self, *args):
        resource_1 = ClassToPatch()
        resource_2 = ClassToPatch()
        self.test_property = (resource_1.some_func(), resource_2.some_func())

class TestCase1(unittest.TestCase):
    @mock.patch('__main__.ClassToPatch', autospec = True)
    def test_1(self, mock1):
        ctpMocks = [mock.Mock(), mock.Mock()]
        ctpMocks[0].some_func.return_value = "funky"
        ctpMocks[1].some_func.return_value = "monkey"
        mock1.side_effect = ctpMocks

        u = UUT()
        self.assertEqual(u.test_property, ("funky", "monkey"))

if __name__ == '__main__':
    unittest.main()

Я добавил test_property в UUT, чтобы модульный тест сделал что-то полезное. Теперь без макета test_property должен быть кортеж, содержащий идентификаторы двух экземпляров ClassToPatch. Но с макетом это должен быть кортеж: ("funky", "monkey").

Я использовал свойство side_effect фиктивного объекта, чтобы разные экземпляры ClassToPatch возвращаются при каждом вызове в инициализаторе UUT.

Надеюсь это поможет.

Изменить: Да, кстати, когда я запускаю модульный тест, я получаю:

.
----------------------------------------------------------------------
Ran 1 test in 0.004s

OK
person srgerg    schedule 25.05.2012
comment
(Новое для Python :) Есть ли способ изменить это для работы с Python 2.7? - person Russ Bateman; 03.02.2015
comment
Я только что запустил этот код в Python 2.7.9 (win32), и он работал нормально. Какую ошибку вы получаете? - person srgerg; 04.02.2015
comment
~ $ python --version Python 2.7.5 AttributeError: ‹модуль 'main' from '/home/russ/dev/eclipse/plugins/org.python.pydev_3.9.0.201411111611/pysrc/runfiles .py'› не имеет атрибута ClassToPatch. - person Russ Bateman; 04.02.2015
comment
@srgerg Вы утверждаете, что макеты вызывались с ожидаемыми вызовами, но вы не утверждаете, что они не вызывались с недопустимыми вызовами. Я знаю, что при создании макета с Mock(spec=an_object) макет будет принимать только те же вызовы, что и an_object, но я не знаю, как это сделать, когда у меня нет экземпляра объекта, например, в этом случае, когда макеты должны быть экземпляры class ClassToPatch - person Alechan; 04.06.2020

Вот еще одна версия, более общая для обработки любого количества созданных экземпляров:

class TestUUT:
    def test_init(self, mocker):
        class MockedClassToPatchMeta(type):
            static_instance = mocker.MagicMock(spec=ClassToPatch)

            def __getattr__(cls, key):
                return MockedClassToPatchMeta.static_instance.__getattr__(key)

        class MockedClassToPatch(metaclass=MockedClassToPatchMeta):
            original_cls = ClassToPatch
            instances = []

            def __new__(cls, *args, **kwargs):
                MockedClassToPatch.instances.append(
                    mocker.MagicMock(spec=MockedClassToPatch.original_cls))
                MockedClassToPatch.instances[-1].__class__ = MockedClassToPatch
                return MockedClassToPatch.instances[-1]

        mocker.patch(__name__ + '.ClassToPatch', new=MockedClassToPatch)

        UUT()

        # since your original code created two instances
        assert 2 == len(MockedClassToPatch.instances)

Если вам нужна более тщательная проверка для каждого экземпляра, вы можете получить доступ к MockedClassToPatch.instances[0] или MockedClassToPatch.instances[1].

Я также создал вспомогательную библиотеку для создания шаблона метакласса. Чтобы сгенерировать необходимый код для вашего примера, я написал:

print(PytestMocker(mocked=ClassToPatch, name=__name__).mock_classes().mock_classes_static().generate())
person Peter K    schedule 24.11.2019