Pytest 嘲讽补丁 - 如何排除故障?

use*_*332 5 python mocking pytest pyarrow

我在使用模拟补丁时遇到了我认为的常见问题,因为我无法找出正确的补丁。

我有两个问题希望得到帮助。

  1. 关于如何解决以下示例中的特定问题的想法
  2. 并且可能是最重要的关于如何最好地解决“我该修补哪一件事”问题的专业提示/指针/想法/建议。我遇到的问题是,在没有完全了解修补工作原理的情况下,我真的不知道我应该寻找什么并发现自己在玩猜谜游戏。

使用pyarrow它的一个例子目前让我感到痛苦:

我的模块.py

import pyarrow

class HdfsSearch:
    def __init__(self):
        self.fs = self._connect()

    def _connect(self) -> object:
        return pyarrow.hdfs.connect(driver="libhdfs")

    def search(self, path: str):
        return self.fs.ls(path=path)
Run Code Online (Sandbox Code Playgroud)

测试模块.py

import pyarrow
import pytest

from mymodule import HdfsSearch

@pytest.fixture()
def hdfs_connection_fixture(mocker):
    mocker.patch("pyarrow.hdfs.connect")
    yield HdfsSearch()

def test_hdfs_connection(hdfs_connection_fixture):
    pyarrow.hdfs.connect.assert_called_once() # <-- succeeds

def test_hdfs_search(hdfs_connection_fixture):
    hdfs_connection_fixture.search(".")
    pyarrow.hdfs.HadoopFileSystem.ls.assert_called_once() # <-- fails
Run Code Online (Sandbox Code Playgroud)

pytest 输出:

$ python -m pytest --verbose test_module.py
=========================================================================================================== test session starts ============================================================================================================
platform linux -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 -- /home/bbaur/miniconda3/envs/dev/bin/python
cachedir: .pytest_cache
rootdir: /home/user1/work/app
plugins: cov-2.7.1, mock-1.10.4
collected 2 items

test_module.py::test_hdfs_connection PASSED                                                                                                                                                                                          [ 50%]
test_module.py::test_hdfs_search FAILED                                                                                                                                                                                              [100%]

================================================================================================================= FAILURES =================================================================================================================
_____________________________________________________________________________________________________________ test_hdfs_search _____________________________________________________________________________________________________________

hdfs_connection_fixture = <mymodule.HdfsSearch object at 0x7fdb4ec2a610>

    def test_hdfs_search(hdfs_connection_fixture):
        hdfs_connection_fixture.search(".")
>       pyarrow.hdfs.HadoopFileSystem.ls.assert_called_once()
E       AttributeError: 'function' object has no attribute 'assert_called_once'

test_module.py:16: AttributeError
Run Code Online (Sandbox Code Playgroud)

Gab*_*lli 5

您不是在 Mock 对象上调用断言,这是正确的断言:

hdfs_connection_fixture.fs.ls.assert_called_once()

解释:

当您访问 Mock 对象中的任何属性时,它将返回另一个 Mock 对象。

由于您打了补丁,"pyarrow.hdfs.connect"您已经用 Mock 替换了它,我们称其为 Mock A。您的_connect方法将返回该 Mock A,然后您将其分配给self.fs

现在让我们分解search调用self.fs.ls.

self.fs返回您的 Mock A 对象,然后.ls将返回一个不同的 Mock 对象,我们称其为 Mock B。在此 Mock B 对象中,您正在执行调用传递(path=path)

在您的断言中,您试图访问pyarrow.hdfs.HadoopFileSystem,但从未修补过。您需要对 Mock B 对象进行断言,该对象位于hdfs_connection_fixture.fs.ls

修补什么

如果您将导入更改mymodule.py为此,from pyarrow.hdfs import connect您的补丁将停止工作。

这是为什么?

当您修补某些东西时,您正在更改 aname指向的内容,而不是实际的对象。

您当前的补丁正在修补名称,pyarrow.hdfs.connect并且在 mymodule 中您使用相同的名称,pyarrow.hdfs.connect因此一切正常。

但是,如果您使用from pyarrow.hdfs import connectmymodule 将导入 realpyarrow.hdfs.connect并为它创建一个名为 的引用mymodule.connect

因此,当您调用connectinside 时,mymodule您正在访问mymodule.connect未修补的 name 。

这就是为什么mymodule.connect在使用 from import 时需要打补丁的原因。

我建议from x import y在进行此类修补时使用。它使您尝试模拟的内容更加明确,并且补丁将仅限于该模块,这可以防止不可预见的副作用。

来源,Python 文档中的这一节:在哪里打补丁