模拟 class 并返回几个值
Mocking a class and returning several values
我有以下配置class:
class ConfigB(object):
Id = None
fileName = None
def __init__(self, file):
self.Id = self.searchForId(file)
self.fileName = file
下面class多次实例化,访问的属性:
from config.ConfigB import ConfigB
class FileRunner(object):
def runProcess(self, cfgA)
for file in cfgA.listFiles:
cfgB = ConfigB(file)
print(cfgB.Id)
print(cfgB.fileName)
为了测试它,我创建了以下测试 class,我在其中为 FileRunner 模拟了 ConfigB class:
import unittest
import unittest.mock imort MagicMock
import mock
from FileRunner import FileRunner
class TestFileRunner(unittest.TestCase):
@mock.patch('FileRunner.ConfigB')
def test_methodscalled(self, cfgB):
cfgA = Mock()
cfgA.listFiles = ['File1','File2']
cfgB().Id.side_effect = [1,2]
cfgB().fileName.side_effect = ['File1','File2']
fileRunner = FileRunner()
fileRunner.runProcess(cfgA)
我正在尝试让 cfgB 的 mock 为 'Id' 和 'fileName' 设置 return 多个值。如果我使用 cfgB().fileName = 'File1'
,我可以将 cfgB 的模拟两次设为 return 'File1',但如果我可以遍历多个 return 值,我会更愿意。有什么可以做的吗?
*编辑:我想明确指出上述测试不适用于 returning 特定值,而是我得到以下输出:
<MagicMock name='cfgB().Id' id='160833320'>
<MagicMock name='cfgB().fileName' id='160833320'>
<MagicMock name='cfgB().Id' id='160833320'>
<MagicMock name='cfgB().fileName' id='160833320'>
这里的问题是您实际上没有按照预期的方式使用 side_effect
。
根据文档 here,side_effect
属性声明:
A function to be called whenever the Mock is called. See the
side_effect attribute. Useful for raising exceptions or dynamically
changing return values. The function is called with the same arguments
as the mock, and unless it returns DEFAULT, the return value of this
function is used as the return value.
这里要实现的关键是函数。这里的期望其实是所谓的东西。您实际上是在测试属性,而属性并没有像函数那样被调用,因此您实际上并没有根据使用这些 side_effect 调用的方式正确配置测试。
根据您要测试的内容,您应该采取稍微不同的方法。查看您的代码,您希望在遍历 cfgA.listFiles
时在循环内创建一个 ConfigB
对象。因此,这表明您实际上正在寻求控制调用 ConfigB(file)
时发生的情况的 side_effect
,您在测试中将其模拟修补为 cfgB
。
此外,您正在将文件名从迭代 cfgA.listFiles
传递到 configB
。因此,您可以将 listFiles
设置为任意文件名的列表:
cfgA.listFiles = ['some_file_name_1', 'some_file_name_2']
然后,您需要做的就是将 cfgB
模拟的 side_effect 设置为现在 return 包含感兴趣属性的 Mock
对象以正确继续与您的测试一样:
cfgB.side_effect = [
Mock(Id="some_id_1", fileName="some_filename_1"),
Mock(Id="some_id_2", fileName="some_filename_2")
]
运行 这些修改将从代码中的打印语句中产生以下结果:
some_id_1
some_filename_1
some_id_2
some_filename_2
因此,如您所见,现在我们已成功设置可迭代对象以保存您要为测试设置的文件名。此外,side_effect
现在可以在 ConfigB
的模拟上正确使用,以便现在 return 正确的模拟配置对象包含您可以在每次迭代中测试的属性。
这是最终测试方法的全部组合:
class TestFileRunner(unittest.TestCase):
@mock.patch('FileRunner.ConfigB')
def test_methodscalled(self, cfgB):
cfgA = Mock()
cfgA.listFiles = ['some_file_name_1', 'some_file_name_2']
cfgB.side_effect = [
Mock(Id="some_id_1", fileName="some_filename_1"),
Mock(Id="some_id_2", fileName="some_filename_2")
]
fileRunner = FileRunner()
fileRunner.runProcess(cfgA)
我有以下配置class:
class ConfigB(object):
Id = None
fileName = None
def __init__(self, file):
self.Id = self.searchForId(file)
self.fileName = file
下面class多次实例化,访问的属性:
from config.ConfigB import ConfigB
class FileRunner(object):
def runProcess(self, cfgA)
for file in cfgA.listFiles:
cfgB = ConfigB(file)
print(cfgB.Id)
print(cfgB.fileName)
为了测试它,我创建了以下测试 class,我在其中为 FileRunner 模拟了 ConfigB class:
import unittest
import unittest.mock imort MagicMock
import mock
from FileRunner import FileRunner
class TestFileRunner(unittest.TestCase):
@mock.patch('FileRunner.ConfigB')
def test_methodscalled(self, cfgB):
cfgA = Mock()
cfgA.listFiles = ['File1','File2']
cfgB().Id.side_effect = [1,2]
cfgB().fileName.side_effect = ['File1','File2']
fileRunner = FileRunner()
fileRunner.runProcess(cfgA)
我正在尝试让 cfgB 的 mock 为 'Id' 和 'fileName' 设置 return 多个值。如果我使用 cfgB().fileName = 'File1'
,我可以将 cfgB 的模拟两次设为 return 'File1',但如果我可以遍历多个 return 值,我会更愿意。有什么可以做的吗?
*编辑:我想明确指出上述测试不适用于 returning 特定值,而是我得到以下输出:
<MagicMock name='cfgB().Id' id='160833320'>
<MagicMock name='cfgB().fileName' id='160833320'>
<MagicMock name='cfgB().Id' id='160833320'>
<MagicMock name='cfgB().fileName' id='160833320'>
这里的问题是您实际上没有按照预期的方式使用 side_effect
。
根据文档 here,side_effect
属性声明:
A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT, the return value of this function is used as the return value.
这里要实现的关键是函数。这里的期望其实是所谓的东西。您实际上是在测试属性,而属性并没有像函数那样被调用,因此您实际上并没有根据使用这些 side_effect 调用的方式正确配置测试。
根据您要测试的内容,您应该采取稍微不同的方法。查看您的代码,您希望在遍历 cfgA.listFiles
时在循环内创建一个 ConfigB
对象。因此,这表明您实际上正在寻求控制调用 ConfigB(file)
时发生的情况的 side_effect
,您在测试中将其模拟修补为 cfgB
。
此外,您正在将文件名从迭代 cfgA.listFiles
传递到 configB
。因此,您可以将 listFiles
设置为任意文件名的列表:
cfgA.listFiles = ['some_file_name_1', 'some_file_name_2']
然后,您需要做的就是将 cfgB
模拟的 side_effect 设置为现在 return 包含感兴趣属性的 Mock
对象以正确继续与您的测试一样:
cfgB.side_effect = [
Mock(Id="some_id_1", fileName="some_filename_1"),
Mock(Id="some_id_2", fileName="some_filename_2")
]
运行 这些修改将从代码中的打印语句中产生以下结果:
some_id_1
some_filename_1
some_id_2
some_filename_2
因此,如您所见,现在我们已成功设置可迭代对象以保存您要为测试设置的文件名。此外,side_effect
现在可以在 ConfigB
的模拟上正确使用,以便现在 return 正确的模拟配置对象包含您可以在每次迭代中测试的属性。
这是最终测试方法的全部组合:
class TestFileRunner(unittest.TestCase):
@mock.patch('FileRunner.ConfigB')
def test_methodscalled(self, cfgB):
cfgA = Mock()
cfgA.listFiles = ['some_file_name_1', 'some_file_name_2']
cfgB.side_effect = [
Mock(Id="some_id_1", fileName="some_filename_1"),
Mock(Id="some_id_2", fileName="some_filename_2")
]
fileRunner = FileRunner()
fileRunner.runProcess(cfgA)