我在使用模拟补丁程序时遇到一个普遍的问题,因为我无法弄清楚要修补的正确的东西。
我有两个问题希望得到帮助。
使用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
答案 0 :(得分:3)
您没有在Mock对象上调用断言,这是正确的断言:
hdfs_connection_fixture.fs.ls.assert_called_once()
说明:
当您访问Mock对象中的任何属性时,它将返回另一个Mock对象。
自从修补"pyarrow.hdfs.connect"
以来,您已经用Mock替换了它,我们称其为MockA。您的_connect
方法将返回该Mock A,并将其分配给self.fs
现在让我们分解一下在调用search
时self.fs.ls
方法中发生的事情。
self.fs
返回您的Mock A对象,然后.ls
将返回另一个Mock对象,我们称其为MockB。在此Mock B对象中,您正在通过{{1} }。
在您的断言中,您尝试访问(path=path)
,但从未对其进行修补。您需要对pyarrow.hdfs.HadoopFileSystem
要修补的内容
如果您将hdfs_connection_fixture.fs.ls
中的导入更改为此mymodule.py
,则补丁将停止工作。
那是为什么?
修补某些内容时,您是在更改from pyarrow.hdfs import connect
指向的内容,而不是实际的对象。
您当前的补丁程序正在修补名称name
,在mymodule中,您使用的名称是pyarrow.hdfs.connect
,所以一切正常。
但是,如果您使用pyarrow.hdfs.connect
,则mymodule将导入真实的from pyarrow.hdfs import connect
并为其创建一个名为pyarrow.hdfs.connect
的引用。
因此,当您在mymodule.connect
内部调用connect
时,您正在访问的名称mymodule
未打补丁。
这就是为什么从导入使用时需要修补mymodule.connect
的原因。
在进行此类修补时,我建议使用mymodule.connect
。它使您要模拟的内容更加明确,并且补丁程序将仅限于该模块,从而可以防止无法预料的副作用。
来源,Python文档中的此部分:Where to patch
答案 1 :(得分:0)
要了解补丁在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 == '.'