Python:模拟上下文管理器
Python: Mocking a context manager
我不明白为什么我不能在这个例子中模拟 NamedTemporaryFile.name:
from mock import Mock, patch
import unittest
import tempfile
def myfunc():
with tempfile.NamedTemporaryFile() as mytmp:
return mytmp.name
class TestMock(unittest.TestCase):
@patch('tempfile.NamedTemporaryFile')
def test_cm(self, mock_tmp):
mytmpname = 'abcde'
mock_tmp.__enter__.return_value.name = mytmpname
self.assertEqual(myfunc(), mytmpname)
测试结果:
AssertionError: <MagicMock name='NamedTemporaryFile().__enter__().name' id='140275675011280'> != 'abcde'
您设置了错误的模拟:mock_tmp
不是上下文管理器,而是 returns 上下文管理器。将您的设置行替换为:
mock_tmp.return_value.__enter__.return_value.name = mytmpname
你的测试会成功。
这里有一个 pytest and mocker fixture 的替代方法,这也是一种常见的做法:
def test_myfunc(mocker):
mock_tempfile = mocker.MagicMock(name='tempfile')
mocker.patch(__name__ + '.tempfile', new=mock_tempfile)
mytmpname = 'abcde'
mock_tempfile.NamedTemporaryFile.return_value.__enter__.return_value.name = mytmpname
assert myfunc() == mytmpname
使用 pytest 和 mocker fixture 扩展 Peter K 的答案。
def myfunc():
with tempfile.NamedTemporaryFile(prefix='fileprefix') as fh:
return fh.name
def test_myfunc(mocker):
mocker.patch('tempfile.NamedTemporaryFile').return_value.__enter__.return_value.name = 'tempfilename'
assert myfunc() == 'tempfilename'
为了扩展 Nathaniel 的回答,此代码块
with tempfile.NamedTemporaryFile() as mytmp:
return mytmp.name
有效地做三件事
# Firstly, it calls NamedTemporaryFile, to create a new instance of the class.
context_manager = tempfile.NamedTemporaryFile()
# Secondly, it calls __enter__ on the context manager instance.
mytmp = context_manager.__enter__()
# Thirdly, we are now "inside" the context and can do some work.
return mytmp.name
当您将 tempfile.NamedTemporaryFile
替换为 Mock
或 MagicMock
的 实例 时
context_manager = mock_tmp()
# This first line, above, will call mock_tmp().
# Therefore we need to set the return_value with
# mock_tmp.return_value
mytmp = context_manager.__enter__()
# This will call mock_tmp.return_value.__enter__() so we need to set
# mock_tmp.return_value.__enter__.return_value
return mytmp.name
# This will access mock_tmp.return_value.__enter__.return_value.name
我将 hmobrienv 的回答扩展到一个小的工作程序
import tempfile
import pytest
def myfunc():
with tempfile.NamedTemporaryFile(prefix="fileprefix") as fh:
return fh.name
def test_myfunc(mocker):
mocker.patch("tempfile.NamedTemporaryFile").return_value.__enter__.return_value.name = "tempfilename"
assert myfunc() == "tempfilename"
if __name__ == "__main__":
pytest.main(args=[__file__])
另一种可能性是使用工厂创建一个实现上下文管理器接口的对象:
import unittest
import unittest.mock
import tempfile
def myfunc():
with tempfile.NamedTemporaryFile() as mytmp:
return mytmp.name
def mock_named_temporary_file(tmpname):
class MockNamedTemporaryFile(object):
def __init__(self, *args, **kwargs):
self.name = tmpname
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
pass
return MockNamedTemporaryFile()
class TestMock(unittest.TestCase):
@unittest.mock.patch("tempfile.NamedTemporaryFile")
def test_cm(self, mock_tmp):
mytmpname = "abcde"
mock_tmp.return_value = mock_named_temporary_file(mytmpname)
self.assertEqual(myfunc(), mytmpname)
我不明白为什么我不能在这个例子中模拟 NamedTemporaryFile.name:
from mock import Mock, patch
import unittest
import tempfile
def myfunc():
with tempfile.NamedTemporaryFile() as mytmp:
return mytmp.name
class TestMock(unittest.TestCase):
@patch('tempfile.NamedTemporaryFile')
def test_cm(self, mock_tmp):
mytmpname = 'abcde'
mock_tmp.__enter__.return_value.name = mytmpname
self.assertEqual(myfunc(), mytmpname)
测试结果:
AssertionError: <MagicMock name='NamedTemporaryFile().__enter__().name' id='140275675011280'> != 'abcde'
您设置了错误的模拟:mock_tmp
不是上下文管理器,而是 returns 上下文管理器。将您的设置行替换为:
mock_tmp.return_value.__enter__.return_value.name = mytmpname
你的测试会成功。
这里有一个 pytest and mocker fixture 的替代方法,这也是一种常见的做法:
def test_myfunc(mocker):
mock_tempfile = mocker.MagicMock(name='tempfile')
mocker.patch(__name__ + '.tempfile', new=mock_tempfile)
mytmpname = 'abcde'
mock_tempfile.NamedTemporaryFile.return_value.__enter__.return_value.name = mytmpname
assert myfunc() == mytmpname
使用 pytest 和 mocker fixture 扩展 Peter K 的答案。
def myfunc():
with tempfile.NamedTemporaryFile(prefix='fileprefix') as fh:
return fh.name
def test_myfunc(mocker):
mocker.patch('tempfile.NamedTemporaryFile').return_value.__enter__.return_value.name = 'tempfilename'
assert myfunc() == 'tempfilename'
为了扩展 Nathaniel 的回答,此代码块
with tempfile.NamedTemporaryFile() as mytmp:
return mytmp.name
有效地做三件事
# Firstly, it calls NamedTemporaryFile, to create a new instance of the class.
context_manager = tempfile.NamedTemporaryFile()
# Secondly, it calls __enter__ on the context manager instance.
mytmp = context_manager.__enter__()
# Thirdly, we are now "inside" the context and can do some work.
return mytmp.name
当您将 tempfile.NamedTemporaryFile
替换为 Mock
或 MagicMock
context_manager = mock_tmp()
# This first line, above, will call mock_tmp().
# Therefore we need to set the return_value with
# mock_tmp.return_value
mytmp = context_manager.__enter__()
# This will call mock_tmp.return_value.__enter__() so we need to set
# mock_tmp.return_value.__enter__.return_value
return mytmp.name
# This will access mock_tmp.return_value.__enter__.return_value.name
我将 hmobrienv 的回答扩展到一个小的工作程序
import tempfile
import pytest
def myfunc():
with tempfile.NamedTemporaryFile(prefix="fileprefix") as fh:
return fh.name
def test_myfunc(mocker):
mocker.patch("tempfile.NamedTemporaryFile").return_value.__enter__.return_value.name = "tempfilename"
assert myfunc() == "tempfilename"
if __name__ == "__main__":
pytest.main(args=[__file__])
另一种可能性是使用工厂创建一个实现上下文管理器接口的对象:
import unittest
import unittest.mock
import tempfile
def myfunc():
with tempfile.NamedTemporaryFile() as mytmp:
return mytmp.name
def mock_named_temporary_file(tmpname):
class MockNamedTemporaryFile(object):
def __init__(self, *args, **kwargs):
self.name = tmpname
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
pass
return MockNamedTemporaryFile()
class TestMock(unittest.TestCase):
@unittest.mock.patch("tempfile.NamedTemporaryFile")
def test_cm(self, mock_tmp):
mytmpname = "abcde"
mock_tmp.return_value = mock_named_temporary_file(mytmpname)
self.assertEqual(myfunc(), mytmpname)