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

使用Python进行模拟时避免冗余的@patch,python,mocking,patch,code-duplication,Python,Mocking,Patch,Code Duplication,来自静态编程语言背景,我想知道如何最好地用Python进行模拟。我习惯于依赖注入。在测试中,将创建模拟并将其传递给被测系统(SUT)。但是,查看用于Python的Mock和其他Mock框架,模块中的类型/函数等似乎是在逐个测试的基础上被替换的 特别是,在Mock中,对于要模拟的每个类型/函数等,在每个单元测试的顶部都会说@patch('some.type.in.the.module.under.test')。在测试的生命周期中,这些东西被模拟,然后被还原。不幸的是,在所有测试中,夹具都非常接近于

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

特别是,在Mock中,对于要模拟的每个类型/函数等,在每个单元测试的顶部都会说
@patch('some.type.in.the.module.under.test')
。在测试的生命周期中,这些东西被模拟,然后被还原。不幸的是,在所有测试中,夹具都非常接近于相同的夹具,最终您会一次又一次地重复您的
@patch
es


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

我不能保证这在语法上是正确的,因为我没有办法测试它,但下面是:

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

您可以修补流向该类中每个方法的测试类。然后您可以从一个超类继承并使用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'。因此,它实际上只有在测试特定模块时才有用。但要测试这个特定的模型,您肯定只需要一个补丁装饰器

我最近遇到了类似的情况,但更极端。我的一个顶级模块必须模拟几个存储库、提供者和逻辑库。这导致了大量的单元测试,需要对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
s在运行时应用于每个测试,并将所有补丁传递到*mocks参数中。要记住的重要一点是排序——我将索引注释放在代码中,以使其在我的头脑中保持清晰


希望这能有所帮助。

我还建议使用装饰程序,因为这样可以避免多余的补丁。不仅如此,使用参数化装饰器,您还可以控制每个装饰器的自定义装置。例如:

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

哈,我一直认为SUT的意思是“测试中的软件”,而不是“系统”。学到了一些新东西!使用像这样的公共函数是一种非常简单的方法。我需要使用共享的fixture构建逻辑每天编写几十次单元测试。我认为装饰师不是解决办法。尽管如此,我认为基于上下文管理器的解决方案也是一样的。我不知道
@patch
对类有效。这是否意味着每个测试方法都必须为每个打补丁的对象设置一个参数?是的,它们都是定位的。在你不在乎他们的情况下,总是会有争论。知道这一点仍然很棒。我看文件的时候都没看到。尽管如此,这仍然需要“每个夹具测试类”,但它正在朝着正确的方向发展。我unittest的方式是,我有许多测试类,它们执行不同类型的设置,然后至少有一个(如果不是更多的话)测试类,这些测试类继承自每个测试模块的各种超类。对我有用。对于每个测试,补丁模块通常是非常具体的,所以我并没有感觉到太多的重复。