模拟 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

根据文档 hereside_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)