Проверьте порядок вызова методов частного класса - PullRequest
0 голосов
/ 13 мая 2018

Здравствуйте!

Tl; dr : мне нужно проверить, что метод открытого класса вызывает методы частного класса в определенном порядке, в зависимости от некоторых условий.Как я могу это сделать?

Учитывая наличие такого класса:

class SomeClass(object):

    def __init__(self, param: ParamsEnum):
        self._param = param

    def process(self):
        if self._param == ParamsEnum.VALUE_1:
            self._do_intermediate_process()
        self._do_process()

    def _do_process(self):
        raise NotImplementedError

    def _do_intermediate_process(self):
        pass

Я хочу написать модульный тест для проверки этой логики процесса.Я пробовал несколько подходов, но ни один из них не удался.

Подход один - с использованием отдельного Mock () в качестве контейнера.

@mock.patch.object(SomeClass, '_do_process')
@mock.patch.object(SomeClass, '_do_intermediate_process')
def test_process(self, mocked_do_process, mocked_do_intermediate_process):
    mock_container = Mock()
    mock_container.m0, mock_container.m1 = mocked_do_process, mocked_do_intermediate_process

    instance = SomeClass(ParamsEnum.VALUE_1)
    instance.process()

    mocked_do_process.assert_called_once()
    mocked_do_intermediate_process.assert_called_once()
    mock_container.assert_has_calls([mock_container.m0, mock_container.m1])

В результате mock_container.method_calls ничего не показывает.

Приближается два - насмешливый «процесс» с сохранением оригинальной логики в качестве побочного эффекта.Этот тест проходит независимо от порядка смоделированных методов в последней строке.

@mock.patch.object(SomeClass, '_do_process')
@mock.patch.object(SomeClass, '_do_intermediate_process')
def test_process(self, mocked_do_process, mocked_do_intermediate_process):
    origignal_process = SomeClass.process
    with mock.patch.object(SomeClass, 'process', autospec=True) as mocked_process:
        def side_effect(self):
            return origignal_process(self)
        mocked_process.side_effect = side_effect

        class_mock = Mock(spec=SomeClass, autospec=True)
        class_mock.process = mocked_process
        class_mock._do_process = mocked_do_process
        class_mock._do_intermediate_process = mocked_do_intermediate_process
        class_mock._param = ParamsEnum.VALUE_1

        class_mock.process(class_mock)

        mocked_do_process.assert_called_once()
        mocked_do_intermediate_process.assert_called_once()
        mocked_process.assert_called_once()
        class_mock.assert_has_calls([mocked_do_intermediate_process, mocked_do_process])

1 Ответ

0 голосов
/ 13 мая 2018

Сначала одна ошибка:

@mock.patch.object(SomeClass, '_do_process')
@mock.patch.object(SomeClass, '_do_intermediate_process')
def test_process(self, mocked_do_process, mocked_do_intermediate_process):

действительно должно быть:

@mock.patch.object(SomeClass, '_do_process')
@mock.patch.object(SomeClass, '_do_intermediate_process')
def test_process(self, mocked_do_intermediate_process, mocked_do_process):

Заказ имеет большое значение.

https://thadeusb.com/weblog/2010/08/23/python_multiple_decorators/

Когда вы помещаете несколько декораторов в функцию python, существует порядок выполнения этих декораторов, который не упоминается в документации.

Декораторы выполняются от самого внутреннего до самого внешнего. Например. Выберите

@i_get_called_last
@i_get_called_second
@i_get_called_first
def my_decorated_function():
    return 1+2

Поэтому при проектировании ваших декораторов убедитесь, что первый и второй вызываемые декораторы возвращают совместимые функции, с которыми могли бы работать предшествующие ему декораторы.

с использованием отдельно Макет () в качестве контейнера.

Он просто не будет работать при создании объекта Mock, его нужно вызывать с помощью mock_container.method() для создания и вызова метода.

mock_container = mock.Mock()
>>>mock_container.called
False
>>> mock_container()
<Mock name='mock()' id='140078609813744'>
>>> mock_container.called
True
>>> mock_container.ma()
<Mock name='mock.ma()' id='140078608707880'>
>>> mock_container.ma.called
True
...