从 __getattr__ 方法检索的模拟函数

Jat*_*mir 5 python unit-testing mocking pytest python-3.x

我正在自动化一些存储库操作,并使用GitPython来完成这项工作。让我们简化一下事情,假设我想断言我的函数是否调用了pull存储库上的方法。代码如下:

from pytest_mock import MockFixture
from git import Git, Repo

repo = Repo('/Users/Jatimir/path/to/repo')

def pull() -> None:
    repo.git.pull()
Run Code Online (Sandbox Code Playgroud)

但是,我注意到该类Git有些特殊并且没有实现pull. 相反,它将所有流量“委托”给__getattr__使用另一种方法来完成这项工作。

def __getattr__(self, name):
    ...
    return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

我的问题是如何进行测试?我将pytestpytest-mock一起使用,它提供了一个mocker固定装置,以下是我的尝试:

def test_pull1(mocker: MockFixture) -> None:
    pull_mock = mocker.MagicMock(name='pull')
    getattr_mock = mocker.MagicMock(name='__getattr__', return_value=pull_mock)

    mocker.patch.object(Git, '__getattr__', getattr_mock)
    pull()
    pull_mock.assert_called_once_with()


def test_pull2(mocker: MockFixture) -> None:
    pull_mock = mocker.Mock(name='pull')

    def __getattr__(self, name):
        if name == 'pull':
            return pull_mock

    mocker.patch.object(Git, '__getattr__', __getattr__)
    pull()
    pull_mock.assert_called_once_with()
Run Code Online (Sandbox Code Playgroud)

它们都有效,但我觉得有更好的方法,也许我的测试方法是错误的。

Jat*_*mir 7

感谢jonrsharpe指导我使用create参数,我成功地通过以下代码实现了我想要的目标:

\n\n
def test_pull(mocker: MockFixture) -> None:\n    m = mocker.patch.object(Git, \'pull\', create=True)\n    pull()\n    m.assert_called_once_with()\n
Run Code Online (Sandbox Code Playgroud)\n\n

摘自文档解释了什么create=True

\n\n
\n

默认情况下 patch() 将无法替换不存在的属性。如果你传入create=True,并且该属性不存在\xe2\x80\x99,patch会在调用patch函数时为你创建该属性,并在之后再次删除它。

\n
\n