14

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

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

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

4

4 回答 4

12

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

于 2012-07-26T08:07:21.310 回答
5

我最近遇到了类似的情况,但更极端。我的顶级模块之一必须模拟几个存储库、提供程序和逻辑库。这导致了许多需要@patch7 个组件的单元测试。我想避免大量重复的测试代码,所以这是我的解决方案,效果很好:

@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 参数。要记住的重要一点是排序 - 我将索引注释放在我的代码中,以保持头脑清醒。

希望这可以帮助。

于 2014-08-23T01:41:17.540 回答
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
于 2012-07-25T20:04:41.567 回答
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
于 2017-10-06T09:47:40.693 回答