Python 单元测试中的模拟FTP
假设我想测试这个非常复杂的函数:Python 单元测试中的模拟FTP,python,ftp,mocking,Python,Ftp,Mocking,假设我想测试这个非常复杂的函数: def func(hostname, username, password): ftp = FTP(hostname, username, password) ftp.retrbinary('RETR README', open('README', 'wb').write) 其中一项测试是: @patch('FTP') def test_func_happy_path(): mock_ftp = Mock() mock_ftp.
def func(hostname, username, password):
ftp = FTP(hostname, username, password)
ftp.retrbinary('RETR README', open('README', 'wb').write)
其中一项测试是:
@patch('FTP')
def test_func_happy_path():
mock_ftp = Mock()
mock_ftp.retrbinary = Mock()
MockFTP.return_value = mock_ftp()
func('localhost', 'fred', 's3Kr3t')
assert mock_ftp.retrbinary.called
但是,这将创建一个名为README的本地文件,我显然不想要它
有没有办法模拟/修补打开
,这样就不会创建任何文件
显然,作为一种解决方法,我可以确保将文件写入,我可以将其作为参数传递给func
,或者在func
中创建并返回
请注意,使用decorator@补丁(“\uuu builtin\uuuu.open”)
,会产生以下期望:
self = <Mock name=u'open()' spec='FTP' id='51439824'>, name = 'write'
def __getattr__(self, name):
if name in ('_mock_methods', '_mock_unsafe'):
raise AttributeError(name)
elif self._mock_methods is not None:
if name not in self._mock_methods or name in _all_magics:
> raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'write'
self=,name='write'
def _ugetattr _;(self,name):
如果名称出现在(“模拟方法”,“模拟不安全”):
提升属性错误(名称)
elif self.\u mock\u方法不是无:
如果名称不在self.\u mock\u方法中或名称不在\u all\u magics中:
>raise AttributeError(“模拟对象没有属性%r”%name)
AttributeError:模拟对象没有属性“write”
我正在向ftp.retrbinary传递回调,而不是函数调用。因此,考虑到您不关心open会发生什么,您可以直接模拟它,使其停止编写。要做到这一点,您可以采用与模拟
FTP
类似的方法。因此,考虑到这一点,您可以如下设置测试代码:
import unittest
from mock import patch, Mock
from my_code import func
class SirTestsAlot(unittest.TestCase):
@patch('my_code.open')
@patch('my_code.FTP')
def test_func_happy_path(self, MockFTP, m_open):
MockFTP.return_value = Mock()
mock_ftp_obj = MockFTP()
m_open.return_value = Mock()
func('localhost', 'fred', 's3Kr3t')
assert mock_ftp_obj.retrbinary.called
assert m_open.called
# To leverage off of the other solution proposed, you can also
# check assert called with here too
m_open.assert_called_once_with('README', 'wb')
if __name__ == '__main__':
unittest.main()
正如你们所看到的,我们在这里所做的是,我们在嘲笑我们正在测试的地方。因此,考虑到这一点,我们正在模拟open
和FTP
关于my_code
现在在my_code
中,没有任何更改:
from ftplib import FTP
def func(hostname, username, password):
ftp = FTP(hostname, username, password)
ftp.retrbinary('RETR README', open('README', 'wb').write)
运行此测试套件将成功返回 另一种方法涉及使用:
或者,您可以使用它来代替写入临时目录。避免写入磁盘,而是将其全部保存在内存中。我想这就是你的目标,对吗?是的,我实际上指的是你用
StringIO
代替open的测试。这样,您就可以避免在进行单元测试时写入磁盘。我正在以自己的方式查看您正在寻找哪种解决方案!:)对于编码机器…@idjaw:需要一首主题曲/音乐来实现…@Sardathrion:现在,只要想象一下旧的蝙蝠侠主题,但用8位音乐蜂鸣代替乐器。(哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔哔,编码!)这样做很好!超级的。我会等一段时间接受它,以防其他人想出一些黑色巫术^_~@Sardathrion真棒!很高兴你成功了。祝你工作顺利。@Sardathrion你有什么特别想做的吗?我可以试一试不,没什么。“我只想在标记某件事被接受前至少24小时离开,因为这会给其他人一些动力,让他们自己想出答案。”萨达思里恩完全同意。事实上,我也很想看到其他的解决方案。
from unittest.mock import patch, mock_open
import ftplib
def func(hostname, username, password):
ftp = ftplib.FTP(hostname, username, password)
ftp.retrbinary('RETR README', open('README', 'wb').write)
@patch('ftplib.FTP')
def test_func_happy_path(MockFTP):
mock_ftp = MockFTP.return_value # returns another `MagicMock`
with patch('__main__.open', mock_open(), create=True) as m:
func('localhost', 'fred', 's3Kr3t')
assert mock_ftp.retrbinary.called
m.assert_called_once_with('README', 'wb')
test_func_happy_path()