Избегайте избыточного @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