模拟 ftplib.FTP 用于单元测试 Python 代码
Mocking ftplib.FTP for unit testing Python code
我不知道为什么我没有得到这个,但我想在 Python 中使用 mock 来测试我的函数是否正确调用了 ftplib.FTP 中的函数。我已经简化了一切,但仍然没有完全理解它是如何工作的。这是一个简单的例子:
import unittest
import ftplib
from unittest.mock import patch
def download_file(hostname, file_path, file_name):
ftp = ftplib.FTP(hostname)
ftp.login()
ftp.cwd(file_path)
class TestDownloader(unittest.TestCase):
@patch('ftplib.FTP')
def test_download_file(self, mock_ftp):
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftp.cwd.assert_called_with('pub/files')
当我 运行 这个时,我得到:
AssertionError: Expected call: cwd('pub/files')
Not called
我知道它一定是在使用模拟对象,因为那是一个伪造的服务器名称,当 运行 没有打补丁时,它会抛出一个 "socket.gaierror" 异常。
如何获得函数 运行ning 的实际对象?长期目标不是在同一文件中使用 "download_file" 函数,而是从单独的模块文件中调用它。
当您执行 patch(ftplib.FTP)
时,您正在修补 FTP
构造函数。 dowload_file()
使用它来 构建 ftp
对象,这样您调用 login()
和 cmd()
的 ftp
对象将是mock_ftp.return_value
而不是 mock_ftp
.
你的测试代码应该如下:
class TestDownloader(unittest.TestCase):
@patch('ftplib.FTP', autospec=True)
def test_download_file(self, mock_ftp_constructor):
mock_ftp = mock_ftp_constructor.return_value
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftp_constructor.assert_called_with('ftp.server.local')
self.assertTrue(mock_ftp.login.called)
mock_ftp.cwd.assert_called_with('pub/files')
我添加了所有检查和 autospec=True
只是因为 good practice
我建议使用 pytest 和 pytest-mock。
from pytest_mock import mocker
def test_download_file(mocker):
ftp_constructor_mock = mocker.patch('ftplib.FTP')
ftp_mock = ftp_constructor_mock.return_value
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
ftp_constructor_mock.assert_called_with('ftp.server.local')
assert ftp_mock.login.called
ftp_mock.cwd.assert_called_with('pub/files')
喜欢, I prefer pytest with mocker。
我走得更远,实际上 wrote a library 这有助于我轻松模拟。以下是如何在您的案例中使用它。
您首先拥有您的代码和一个基本的 pytest 函数,添加我的帮助程序库以生成对模块的模拟和匹配的断言生成:
import ftplib
from mock_autogen.pytest_mocker import PytestMocker
def download_file(hostname, file_path, file_name):
ftp = ftplib.FTP(hostname)
ftp.login()
ftp.cwd(file_path)
def test_download_file(mocker):
import sys
print(PytestMocker(mocked=sys.modules[__name__],
name=__name__).mock_modules().prepare_asserts_calls().generate())
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
当您第一次 运行 测试时,它会由于未知的 DNS 而失败,但是包装我的库的打印语句会给我们这个有价值的输入:
...
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch('test_29817963.ftplib', new=mock_ftplib)
...
import mock_autogen
...
print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
我正在将其放入测试中并会再次运行:
def test_download_file(mocker):
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch('test_29817963.ftplib', new=mock_ftplib)
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
import mock_autogen
print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
这次测试成功,我只需要收集第二次打印的结果就可以得到正确的断言:
def test_download_file(mocker):
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch(__name__ + '.ftplib', new=mock_ftplib)
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftplib.FTP.assert_called_once_with('ftp.server.local')
mock_ftplib.FTP.return_value.login.assert_called_once_with()
mock_ftplib.FTP.return_value.cwd.assert_called_once_with('pub/files')
如果您想在使用我的图书馆的同时继续使用 unittest
,我是 accepting pull requests。
我不知道为什么我没有得到这个,但我想在 Python 中使用 mock 来测试我的函数是否正确调用了 ftplib.FTP 中的函数。我已经简化了一切,但仍然没有完全理解它是如何工作的。这是一个简单的例子:
import unittest
import ftplib
from unittest.mock import patch
def download_file(hostname, file_path, file_name):
ftp = ftplib.FTP(hostname)
ftp.login()
ftp.cwd(file_path)
class TestDownloader(unittest.TestCase):
@patch('ftplib.FTP')
def test_download_file(self, mock_ftp):
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftp.cwd.assert_called_with('pub/files')
当我 运行 这个时,我得到:
AssertionError: Expected call: cwd('pub/files')
Not called
我知道它一定是在使用模拟对象,因为那是一个伪造的服务器名称,当 运行 没有打补丁时,它会抛出一个 "socket.gaierror" 异常。
如何获得函数 运行ning 的实际对象?长期目标不是在同一文件中使用 "download_file" 函数,而是从单独的模块文件中调用它。
当您执行 patch(ftplib.FTP)
时,您正在修补 FTP
构造函数。 dowload_file()
使用它来 构建 ftp
对象,这样您调用 login()
和 cmd()
的 ftp
对象将是mock_ftp.return_value
而不是 mock_ftp
.
你的测试代码应该如下:
class TestDownloader(unittest.TestCase):
@patch('ftplib.FTP', autospec=True)
def test_download_file(self, mock_ftp_constructor):
mock_ftp = mock_ftp_constructor.return_value
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftp_constructor.assert_called_with('ftp.server.local')
self.assertTrue(mock_ftp.login.called)
mock_ftp.cwd.assert_called_with('pub/files')
我添加了所有检查和 autospec=True
只是因为 good practice
我建议使用 pytest 和 pytest-mock。
from pytest_mock import mocker
def test_download_file(mocker):
ftp_constructor_mock = mocker.patch('ftplib.FTP')
ftp_mock = ftp_constructor_mock.return_value
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
ftp_constructor_mock.assert_called_with('ftp.server.local')
assert ftp_mock.login.called
ftp_mock.cwd.assert_called_with('pub/files')
喜欢
我走得更远,实际上 wrote a library 这有助于我轻松模拟。以下是如何在您的案例中使用它。
您首先拥有您的代码和一个基本的 pytest 函数,添加我的帮助程序库以生成对模块的模拟和匹配的断言生成:
import ftplib
from mock_autogen.pytest_mocker import PytestMocker
def download_file(hostname, file_path, file_name):
ftp = ftplib.FTP(hostname)
ftp.login()
ftp.cwd(file_path)
def test_download_file(mocker):
import sys
print(PytestMocker(mocked=sys.modules[__name__],
name=__name__).mock_modules().prepare_asserts_calls().generate())
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
当您第一次 运行 测试时,它会由于未知的 DNS 而失败,但是包装我的库的打印语句会给我们这个有价值的输入:
...
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch('test_29817963.ftplib', new=mock_ftplib)
...
import mock_autogen
...
print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
我正在将其放入测试中并会再次运行:
def test_download_file(mocker):
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch('test_29817963.ftplib', new=mock_ftplib)
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
import mock_autogen
print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
这次测试成功,我只需要收集第二次打印的结果就可以得到正确的断言:
def test_download_file(mocker):
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch(__name__ + '.ftplib', new=mock_ftplib)
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftplib.FTP.assert_called_once_with('ftp.server.local')
mock_ftplib.FTP.return_value.login.assert_called_once_with()
mock_ftplib.FTP.return_value.cwd.assert_called_once_with('pub/files')
如果您想在使用我的图书馆的同时继续使用 unittest
,我是 accepting pull requests。