【问题标题】:How to mock a @property method with "self" as parameter?如何以“self”为参数模拟@property 方法?
【发布时间】:2021-08-27 07:12:46
【问题描述】:

我正在尝试仅在类的特定实例上模拟属性。为了简化示例,我有一个Thing,它使用name 进行初始化,并且基于此名称,该类将在预定义的位置/conf/{name}_conf.yaml 中加载配置文件。

在测试时,会创建几个 Thing 实例,我只想覆盖其中一个的配置。

我在下面评论了我必须更改才能使其工作的初始代码:

class Thing():

    def __init__(self, name):
        self.name = name
        # I wasn't able to mock this:
        # self.configuration_name = f'/configuration/{self.name}_configuration.yaml'

    # @property   <- nor this
    def configuration_filename(self):
        return f'/configuration/{self.name}_configuration.yaml'

在我的测试中,模拟应该能够将不同的配置文件(特定于测试)作为参数,但只能应用于名为 test_thingThing 实例。

我得到了它与上面的实现这样的工作:

configuration_filename_orig = Thing.configuration_filename

def my_patched_configuration_filename(self, configuration_filename, *args, **kwargs):
    if self.slug == 'cmstest':
        return configuration_filename
    else:
        return configuration_filename_orig(self, *args, **kwargs)

然后我可以像这样为每个测试类“注入”一个自定义测试配置文件:

from functools import partial
from test.utils import my_patched_configuration_filename
...

@patch.object(Thing, 'configuration_filename', autospec=True, side_effect=partial(my_patched_configuration_filename, configuration_filename='testdata/test_1.yaml'))
class ConfigurationTests(TestCase):

     def test_1(self, mocked_conf):
         # test something

    def test_2(self, mocked_conf):
         # test something else

@patch.object(Thing, 'configuration_filename', autospec=True, side_effect=partial(my_patched_configuration_filename, configuration_filename='testdata/test_2.yaml'))
class ConfigurationTestsAdvanced(TestCase):

     def test_1(self, mocked_conf):
         # test something

    def test_2(self, mocked_conf):
         # test something else

现在...这可行,但我想知道是否有办法做类似的事情,但在 Thing 类上具有真实属性(使用 @property 装饰器或在 __init__ 中初始化的属性方法)。

我花了几个小时尝试不同的东西......但主要问题似乎是使用 return_value 不会将 self 参数传递给模拟,所以我不能使用它。

有什么想法吗?

【问题讨论】:

  • 您是否使用property mock 模拟该属性?
  • 我试过了,但据我所知PropertyMock 接受return_value,我需要它根据实例起作用(需要self arg)。如果我使用带有副作用的PropertyMock,我也不会得到self。如果PropertyMock有办法,我还没找到:(

标签: python unit-testing mocking


【解决方案1】:

好的,可能有更好的方法,但我的工作如下:

  • 我可以在课堂上使用 @property 装饰器,这就是我想要模拟的:
class Thing():

    def __init__(self, name):
        self.name = name

    @property
    def configuration_filename(self):
        return f'/configuration/{self.name}_configuration.yaml'
  • 我创建了一个新的模拟类,基于Mock

# first copy original
configuration_filename_orig = Thing.configuration_filename.__get__


class ConfigurationPropertyMock(Mock):

    # and here, add the `self` to the args
    def __get__(self, obj, obj_type=None, *args, **kwargs):
        return self(obj, obj_type, *args, **kwargs)


def patched_filename(self, *args, **kwargs):
    configuration_filename = kwargs.pop('configuration_filename')
    if self.slug == 'cmstest' and configuration_filename:
        return configuration_filename
    else:
        return configuration_filename_orig(self, *args, **kwargs)
  • 我修补了测试类,我可以在其中通过自定义configuration_filename
from unittest.mock import patch
from tests.utils import ConfigurationPropertyMock, patched_filename
...

@patch('somewhere.Thing.configuration_filename',
       new_callable=ConfigurationPropertyMock,
       side_effect=partial(patched_filename, configuration_filename='test_conf.yaml'))
       )
class YAMLApiConfigurationTests(TestCase):

    def test_api_configuration_document(self, mocked_conf):
        # test here, the test configuration is loaded
        pass

瞧 :)

【讨论】:

  • 我认为这是一个合理的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-30
  • 1970-01-01
  • 2020-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-11
相关资源
最近更新 更多