使用Python进行模拟时避免使用冗余@patch

问题描述 投票:11回答:4

来自静态编程语言背景,我想知道如何最好地在Python中进行模拟。我习惯于依赖注入。在测试中,模拟被创建并传递给被测系统(SUT)。但是,看看Mock和Python的其他模拟框架,它似乎是类型/函数/等。在逐个测试的基础上更换模块中的模块。

特别是,对于Mock,在每个单元测试的顶部,你会说每种类型/功能/等的@patch('some.type.in.the.module.under.test')。你想嘲笑在测试的一生中,这些东西被嘲笑,然后它们被还原。不幸的是,在测试中,夹具非常接近,你最终会一遍又一遍地重复你的@patches。

我想要一种方法来跨单元测试共享补丁集合。我还希望以可组合的方式对夹具进行调整。我可以使用上下文管理器而不是装饰器。

python mocking patch code-duplication
4个回答
9
投票

您可以修补流向该类中每个方法的测试类。然后你可以从超类继承并使用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'。所以它在测试特定模块时才真正有用。但是为了测试那个特定的模型,你当然只需要一个补丁修饰器。


3
投票

我最近遇到了类似的情况但更极端。我的一个顶级模块必须模拟出几个存储库,提供程序和逻辑库。这导致了@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

类上的@mocks在运行时应用于每个测试,并将所有补丁传递到* mocks参数。要记住的重要事项是排序 - 我将索引注释放在我的代码中,以便将它直接放在我的脑海中。

希望这可以帮助。


1
投票

我不保证这在语法上是正确的,因为我无法测试它,但是这里是:

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

1
投票

我也会推荐装饰器,因为你可以避免多余的补丁。而且不仅如此,使用参数化装饰器,您可以控制每个装饰器的自定义装置。例:

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
© www.soinside.com 2019 - 2024. All rights reserved.