Избегайте избыточного @patch при срыве с Python

Исходя из статического языка программирования, мне интересно, как лучше всего насмехаться над Python. Я привык к инъекции зависимостей. В рамках тестов mocks создаются и передаются в Системный тест (SUT). Однако, глядя на Mock и другие макетные рамки для Python, кажется, что типы/функции/и т.д. в модуле заменяются на тестовом основании.

В частности, с помощью Mock на каждом unit test вы говорите @patch('some.type.in.the.module.under.test') для каждого типа/функции/и т.д. вы хотите насмехаться. Для жизни теста эти вещи насмехаются, тогда они возвращаются. К сожалению, во всех тестах прибор довольно близок к тому же, и вы снова и снова повторяете свои @patch es.

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

Ответ 1

Вы можете исправить тестовый класс, который переходит к каждому методу этого класса. И тогда вы можете наследовать суперкласс и работать с методами setUp и tearDown.

import unittest 

@patch('some.type.in.the.module.under.test')
class MySuperTestCase(unittest.TestCase):
    pass

class MyActualTestCase(MySuperTestCase):

    def test_method(self, mock_function)
        mock_function.return_value = False

Это менее общий, чем вы думаете. Потому что вам нужно исправить объект в том месте, где он используется. Вы не устанавливаете 'sys.stdout', вы устанавливаете 'my_dir.my_module.sys.stdout'. Таким образом, это действительно будет полезно при тестировании определенного модуля. Но для проверки этой конкретной модели вам может понадобиться только один патч-декоратор.

Ответ 2

Недавно я столкнулся с подобной ситуацией, но более экстремальной. Один из моих модулей верхнего уровня должен был издеваться над несколькими репозиториями, поставщиками и логическими библиотеками. Это привело к ряду модульных тестов, необходимых для @patch 7 компонентов. Я хотел избежать много повторяющегося тестового кода, так что вот мое решение, которое работало довольно хорошо:

@mock.patch('module.blah1.method1')      # index: 6
@mock.patch('module.blah1.method2')      # index: 5
@mock.patch('module.blah2.method1')      # index: 4
@mock.patch('module.blah2.method2')      # index: 3
@mock.patch('module.blah2.method3')      # index: 2
@mock.patch('module.blah3.method1')      # index: 1
@mock.patch('module.blah4.method1')      # index: 0
class TestsForMyCode(unittest.TestCase):

    def test_first_test(self, *mocks):
        # Arrange

        # setup mocks for only the ones that need specific mocked behaviours

        # idx 2 patches module.blah2.method3
        mocks[2].return_value = 'some value'

        # Act
        target = sut()
        result = target.do_something()

        # Assert
        assert result is False

    def test_second_test(self, *mocks):
        # Arrange

        # setup mocks for only the ones that need specific mocked behaviours

        # idx 0 patches module.blah4.method1
        mocks[0].return_value = 'another value'

        # idx 4 patches module.blah2.method1
        mocks[4].return_value = 'another value'

        # Act
        target = sut()
        result = target.do_something_else()

        # Assert
        assert result is True

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

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

Ответ 3

Я не гарантирую, что это синтаксически правильно, так как у меня нет возможности проверить его, но здесь идет:

COMMON_FUNCTIONS = ('some.type.in.the.module.under.test', 'and.others')
def common_patches(f):
    for item in COMMON_FUNCTIONS:
        f = patch(item)(f)

Теперь примените его с помощью:

@common_patches
def something():
    pass # it will be decorated by all the patches in the function

Ответ 4

Я бы также рекомендовал декораторы, так как вы можете избежать избыточного патча. И не только это, используя параметризованные декораторы, вы можете управлять настраиваемыми светильниками для каждого декоратора. Пример:

def patch_example(custom_value=None):
    def _patch(test_func):
        @mock.patch('some.type.in.the.module.under.test')
        def _patch_it(mocked_function):
            mocked_function = custom_value
            return test_func(self)
        return wraps(test_func)(_patch_it)
    return _patch

class ExampleTestCase(object):

    @patch_example(custom_value='new_value')
    def test_method_1(self):
        # your test logic here, with mocked values already defined in decorator

    @patch_example(custom_value='new_value_2')
    def test_method_2(self):
        # your test logic here, with mocked values already defined in decorator