如何提高@patch和MagicMock语句的可读性和可维护性(避免长名称和字符串标识)?

2024-03-30

在我的测试代码中,我有很多样板表达式“Magic”、“return_”。我还有很长的字符串来标识要模拟的函数的路径。 重构期间不会自动替换字符串,我更愿意直接使用导入的函数。

示例代码:

from mock import patch, MagicMock
from pytest import raises

@patch(
    'foo.baa.qux.long_module_name.calculation.energy_intensity.intensity_table',
    MagicMock(return_value='mocked_result_table'),
)

相反,我更喜欢:

from better_test_module import patch, Mock, raises
from foo.baa.qux.long_module_name.calculation import energy_intensity

@patch(
    energy_intensity.intensity_table,
    Mock('mocked_result_table'),
)

or

@patch(
    energy_intensity.intensity_table,
    'mocked_result_table',
)

我将相应的自定义实现作为答案发布在下面。

如果您有其他建议,请告诉我。我想知道为什么建议的解决方案不是默认的。我不想重新发明轮子。 因此,如果有我可以使用的现有库,请告诉我。

Related:

Mock 与 MagicMock https://stackoverflow.com/questions/17181687/mock-vs-magicmock

如何在 MagicMock 子类上重写 __getitem__ https://stackoverflow.com/questions/67580353/how-to-override-getitem-on-a-magicmock-subclass


创建一个包装器模块,允许使用较短的名称并直接传递函数。 (如果类似的东西已经作为 pip 包存在,请告诉我;不想重新发明轮子。)

Usage:

from my_test_utils.mock import patch, Mock, raises    
from foo.baa.qux.long_module_name.calculation import energy_intensity

@patch(
    energy_intensity.intensity_table,
    Mock('mocked_result_table'),  
)

我的包装代码的初稿my_test_utils/mock.py:

from mock import MagicMock, DEFAULT
from mock import patch as original_patch
from pytest import raises as original_raises


class Mock(MagicMock):
    # This class serves as a wrapper for MagicMock to allow for shorter syntax

    def __new__(cls, *args, **kwargs):
        if len(args) > 0:
            first_argument = args[0]
            mock = MagicMock(return_value=first_argument, *args[1:], **kwargs)
        else:
            mock = MagicMock(**kwargs)
        return mock

    def assert_called_once(self, *args, **kwargs):  # pylint: disable = useless-parent-delegation
        # pylint did not find this method without defining it as a proxy
        super().assert_called_once(*args, **kwargs)

    def assert_not_called(self, *args, **kwargs):  # pylint: disable = useless-parent-delegation
        # pylint did not find this method without defining it as a proxy
        super().assert_not_called(*args, **kwargs)


def patch(item_to_patch, *args, **kwargs):
    if isinstance(item_to_patch, str):
            raise KeyError('Please import and use items directly instead of passing string paths!')

    module_path = item_to_patch.__module__
    if hasattr(item_to_patch, '__qualname__'):
        item_path = module_path + '.' + item_to_patch.__qualname__
    else:
        name = _try_to_get_object_name(item_to_patch, module_path)
        item_path = module_path + '.' + name

    item_path = item_path.lstrip('_')

    return original_patch(item_path, *args, **kwargs)


def _try_to_get_object_name(object_to_patch, module_path):
    module = __import__(module_path)
    name = None
    for attribute_name in dir(module):
        attribute = getattr(module, attribute_name)
        if attribute == object_to_patch:
            if name is None:
                name = attribute_name
            else:
                # object is not unique within its parent but used twice
                message = (
                    'Could not identify item to patch because object is not unique.'
                    + ' Please use a unique string path.'
                )
                raise KeyError(message)
    if name is None:
        raise KeyError('Could not identify object to patch.')
    return name
    



def raises(*args):
    # This function serves as a wrapper for raises to be able to import it from the same module as the other functions
    return original_raises(*args)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何提高@patch和MagicMock语句的可读性和可维护性(避免长名称和字符串标识)? 的相关文章

随机推荐