如何修补由 Python 数据形状中的装饰器注册的方法?

Posted

技术标签:

【中文标题】如何修补由 Python 数据形状中的装饰器注册的方法?【英文标题】:How do I patch a method registered by a decorator in Python's datashape? 【发布时间】:2016-09-18 07:28:41 【问题描述】:

我正在使用datashape Python 包并使用@datashape.discover.register 装饰器注册一个新类型。我想测试一下,当我在我正在注册的类型的对象上调用 datashape.discover 时,它会调用被装饰的函数。我还想用良好的单元测试原则来做到这一点,这意味着实际上不执行被修饰的函数,因为它会产生我不希望在测试中产生的副作用。但是,这不起作用。

这里有一些示例代码来演示这个问题:

我的文件.py:

@datashape.discover.register(SomeType)
def discover_some_type(data)
    ...some stuff i don't want done in a unit test...

test_myfile.py:

class TestDiscoverSomeType(unittest.TestCase):
    @patch('myfile.discover_some_type')
    def test_discover_some_type(self, mock_discover_some_type):
        file_to_discover = SomeType()

        datashape.discover(file_to_discover)

        mock_discover_some_type.assert_called_with(file_to_discover)

问题似乎是我想要模拟的函数在测试主体中被模拟,但是,它在装饰时(即导入时)没有被模拟。 discover.register 函数本质上是在内部注册被修饰的函数,以便在使用给定类型的参数调用 discover() 时查找它。不幸的是,它似乎每次都在内部注册 real 函数,而不是我想要的补丁版本,所以它总是会调用 real 函数。

关于如何修补被修饰的函数并断言在调用datashape.discover 时调用它有什么想法吗?

【问题讨论】:

【参考方案1】:

这是我发现的一个有点hacky的解决方案:

sometype.py:

def discover_some_type(data):
    ...some stuff i don't want done in a unit test...

discovery_channel.py:

import sometype

@datashape.discover.register(SomeType)
def discover_some_type(data):
    return sometype.discover_some_type(data)

test_sometype.py:

class TestDiscoverSomeType(unittest.TestCase):
    @patch('sometype.discover_some_type')
    def test_discover_some_type(self, mock_discover_some_type):
        import discovery_channel

        file_to_discover = SomeType()

        datashape.discover(file_to_discover)

        mock_discover_some_type.assert_called_with(file_to_discover)

关键是,您必须在导入具有修饰函数的模块之前修补任何实际会做的事情,该修饰函数会将修补函数注册到 datashape。不幸的是,这意味着你不能让你的装饰函数和在同一个模块中进行发现的函数(所以逻辑上应该在一起的东西现在分开了)。而且您在单元测试中有一些 hacky import-in-a-function(触发discover.register)。但至少它有效

【讨论】:

一点也不 hacky。我的偏好是使用 mock.patch 作为上下文管理器,这意味着您将在 with patch(...): block 中进行导入。

以上是关于如何修补由 Python 数据形状中的装饰器注册的方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用装饰器修补sys属性?

如何使用 Python 装饰器以便方法使用 functools.lru_cache 并自行注册?

python语言中的装饰器详解

Python装饰器详解,详细介绍它的应用场景

python进阶之装饰器之2.定义一个可接受参数的装饰器如何定义一个属性可由用户修改的装饰器定义一个能接受可选参数的装饰器

Python 联合 - 在另一个装饰器中收集多个 @patch 装饰器