我认为使用模拟补丁时遇到一个常见问题,因为我无法找出要修补的正确的事情。
我有两个问题希望得到帮助。
使用
pyarrow
的示例目前让我感到痛苦:
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)
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
$ 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
您没有在 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
,您的补丁将停止工作。
这是为什么呢?
当您修补某些内容时,您正在更改
name
所指向的内容,而不是实际的对象。
您当前的补丁正在修补名称
pyarrow.hdfs.connect
,并且在我的模块中您使用相同的名称pyarrow.hdfs.connect
,所以一切都很好。
但是,如果您使用
from pyarrow.hdfs import connect
,我的模块将导入真正的 pyarrow.hdfs.connect
并为其创建一个名为 mymodule.connect
的引用。
因此,当您在
connect
内调用 mymodule
时,您正在访问未修补的名称 mymodule.connect
。
这就是为什么在使用 from import 时需要修补
mymodule.connect
。
我建议在进行此类修补时使用
from x import y
。它使您尝试模拟的内容更加明确,并且补丁将仅限于该模块,这可以防止不可预见的副作用。
来源,Python 文档中的这一部分:在哪里打补丁
要了解 python 中的修补工作原理,我们首先了解 import 语句。
当我们在模块(本例中为 mymodule.py)中使用
import pyarrow
时,它会执行两个操作:
pyarrow
中搜索
sys.modules
pyarrow
)。
通过执行以下操作:pyarrow = sys.modules['pyarrow']
注意:Python 中的
import
语句不执行代码。 import 语句将名称带入本地范围。仅当 python 在 sys.modules 中找不到模块时,代码的执行才会作为副作用发生
因此,要修补在 mymodule.py 中导入的 pyarrow 我们需要修补 mymodule.py 本地范围中存在的
pyarrow
名称
patch('mymodule.pyarrow', autospec=True)
test_module.py
import pytest
from mock import Mock, sentinel
from pyarrow import hdfs
from mymodule import HdfsSearch
class TestHdfsSearch(object):
@pytest.fixture(autouse=True, scope='function')
def setup(self, mocker):
self.hdfs_mock = Mock(name='HadoopFileSystem', spec=hdfs.HadoopFileSystem)
self.connect_mock = mocker.patch("mymodule.pyarrow.hdfs.connect", return_value=self.hdfs_mock)
def test_initialize_HdfsSearch_should_connect_pyarrow_hdfs_file_system(self):
HdfsSearch()
self.connect_mock.assert_called_once_with(driver="libhdfs")
def test_initialize_HdfsSearch_should_set_pyarrow_hdfs_as_file_system(self):
hdfs_search = HdfsSearch()
assert self.hdfs_mock == hdfs_search.fs
def test_search_should_retrieve_directory_contents(self):
hdfs_search = HdfsSearch()
self.hdfs_mock.ls.return_value = sentinel.contents
result = hdfs_search.search(".")
self.hdfs_mock.ls.assert_called_once_with(path=".")
assert sentinel.contents == result
使用上下文管理器修补内置程序
def test_patch_built_ins():
with patch('os.curdir') as curdir_mock: # curdir_mock lives only inside with block. Doesn't lives outside
assert curdir_mock == os.curdir
assert os.curdir == '.'
如果在我的 mymodule.py 中,不是在对象中调用 pyarrow,而是在声明性语句中调用它会怎样:
conn = pyarrow.hdfs.connect(driver="libhdfs")
我仍然可以在 test_module.py 中模拟/修补 pyarrow 吗?