如何模拟模块内同名函数中调用的函数?

lmi*_*asf 3 python mocking python-unittest

我正在尝试使用unittest.mock,但出现错误:

\n\n
\n

AttributeError:没有属性“get_pledge_Frequency”

\n
\n\n

我有以下文件结构:

\n\n
pledges/views/\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 util.py\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 user_profile.py\npledges/tests/unit/profile\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 test_user.py\n
Run Code Online (Sandbox Code Playgroud)\n\n

里面pledges/views/__init___.py我有:

\n\n
from .views import *\nfrom .account import account\nfrom .splash import splash\nfrom .preferences import preferences\nfrom .user_profile import user_profile\n
Run Code Online (Sandbox Code Playgroud)\n\n

在里面,user_profile.py我有一个名为的函数user_profile,它调用内部的函数util.py,如下get_pledge_frequency所示:

\n\n
def user_profile(request, user_id):\n    # some logic\n\n    # !!!!!!!!!!!!!!!!\n    a, b = get_pledge_frequency(parameter) # this is the function I want to mock\n\n    # more logic\n\n    return some_value\n
Run Code Online (Sandbox Code Playgroud)\n\n

我里面有一个测试test_user.py如下:

\n\n
def test_name():\n    with mock.patch(\n        "pledges.views.user_profile.get_pledge_frequency"\n    ) as get_pledge_frequency:\n        get_pledge_frequency.return_value = ([], [])\n        response = c.get(\n            reverse("pledges:user_profile", kwargs={"user_id": user.id})\n            ) # this calls the function user_profile inside pledges.user_profile\n\n     # some asserts to verify functionality\n
Run Code Online (Sandbox Code Playgroud)\n\n

我已经检查了其他问题,但答案不包括何时有一个称为模块的函数并将其导入到文件中__init__

\n\n

那么,有没有办法解决这个问题呢?我基本上已将文件重命名user_profile.pyprofile,然后更改了测试以引用该模块内的函数,但我想知道是否可以保留该函数和模块具有相同的名称。

\n

Ste*_*uch 6

事实证明,可以模拟在同名模块内的函数中调用的函数。一个小包装unittest.mock.patch()可以使这种情况发生,如下所示:

代码:

from unittest import mock
import importlib

def module_patch(*args):
    target = args[0]
    components = target.split('.')
    for i in range(len(components), 0, -1):
        try:
            # attempt to import the module
            imported = importlib.import_module('.'.join(components[:i]))

            # module was imported, let's use it in the patch
            patch = mock.patch(*args)
            patch.getter = lambda: imported
            patch.attribute = '.'.join(components[i:])
            return patch
        except Exception as exc:
            pass

    # did not find a module, just return the default mock
    return mock.patch(*args)
Run Code Online (Sandbox Code Playgroud)

使用方法:

代替:

mock.patch("module.a.b")
Run Code Online (Sandbox Code Playgroud)

你需要:

module_patch("module.a.b")
Run Code Online (Sandbox Code Playgroud)

这是如何运作的?

基本思想是尝试从最长的可能模块路径开始向最短路径导入模块,如果导入成功,则使用该模块作为修补对象。

测试代码:

import module

print('module.a(): ', module.a())
print('module.b(): ', module.b())
print('--')

with module_patch("module.a.b") as module_a_b:
    module_a_b.return_value = 'new_b'
    print('module.a(): ', module.a())
    print('module.b(): ', module.b())

try:
    mock.patch("module.a.b").__enter__()
    assert False, "Attribute error was not raised, test case is broken"
except AttributeError:
    pass
Run Code Online (Sandbox Code Playgroud)

测试文件在module

# __init__.py
from .a import a
from .a import b


# a.py
def a():
    return b()

def b():
    return 'b'
Run Code Online (Sandbox Code Playgroud)

结果:

module.a():  b
module.b():  b
--
module.a():  new_b
module.b():  b
Run Code Online (Sandbox Code Playgroud)