2017-06-19 68 views
1

我有以下的配置类:嘲笑类和返回几个值

class ConfigB(object): 
    Id = None 
    fileName = None 

    def __init__(self, file): 
    self.Id = self.searchForId(file) 
    self.fileName = file 

这是在下面的类实例化多次和属性进行访问:

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) 

为了测试它,我创建以下测试类,我模拟ConfigB为FileRunner类:

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获取模拟以为'Id'和'fileName'返回多个值。如果我使用cfgB().fileName = 'File1',我可以得到cfgB的模拟函数来返回'File1'两次,但我更喜欢如果我可以遍历多个返回值。是可以做的事吗?

*编辑:我想明确指出,上述测试没有返回的特定值的工作,而是我得到下面的输出:

<MagicMock name='cfgB().Id' id='160833320'> 
<MagicMock name='cfgB().fileName' id='160833320'> 
<MagicMock name='cfgB().Id' id='160833320'> 
<MagicMock name='cfgB().fileName' id='160833320'> 
+0

有几个与你的代码的问题:1.第一种方法是''__init__''而不是''__Init__' '; 2.''Id''和''FileName''是类变量,可能会导致意外的结果,如[这里]所述(https://docs.python.org/3.6/tutorial/classes.html#class-and-实例变量)。您可以简单地删除它们,只在'__init__''方法中留下分配。这**可能会解决你的问题(我没有测试过,它可能不是解决方案) –

+0

'__init__'和'__Init__'是一个不在我的实际代码中的错字,我在上面修复了它。我不想离开'__init__'方法,因为实际的'__init__'方法使得我的代码复杂得多,所以我宁愿只模拟返回值。 – EliSquared

回答

1

这里的问题是,你实际上并没有使用side_effect它打算使用的方式。

每文档here,该side_effect属性状态:

的函数,只要素被称为被调用。请参阅 side_effect属性。用于引发异常或动态地更改返回值 。该函数的调用与模拟的参数 相同,除非它返回DEFAULT,否则此函数的返回值用作返回值。

这里要实现的关键是函数。这里的预期实际上是,被称为。实际上,您正在测试属性,并且属性不会像函数那样被调用,所以您实际上并未正确地配置测试,因为您正在使用这些side_effect调用。

根据您要测试的内容,您应该采取稍微不同的方法。看着你的代码,当你遍历cfgA.listFiles时,你正在寻找在你的循环内创建一个ConfigB对象。所以,这表明你实际上想要控制side_effect当你打电话ConfigB(file),你在你的测试中假装修补为cfgB

此外,你正在通过似乎从cfgA.listFiles迭代到configB迭代的文件名。因此,你可以设置listFiles为为任意文件名列表:

cfgA.listFiles = ['some_file_name_1', 'some_file_name_2'] 

然后,所有你需要做的,然后设置你的cfgB模拟的side_effect到现在返回一个包含感兴趣的属性的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,为了现在返回适当的模拟配置对象,持有您可以在每次迭代中测试的属性。

这里是最后的测试方法是什么样子都放在一起:

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) 
+0

这是我想要做的大部分工作,但现在我有一个最后的问题,因为这在我的单元测试中引起了意想不到的问题。我在文件runner类'foo(cfgB)'中有一个函数,我想确认是使用正确的模拟函数调用的。如果我做了'foo.assert_called_with(cfgB())',我会得到一个可迭代的错误,除非我将第三行添加到'Mock(Id =“some_id_3”,fileName =“some_filename_3”)的side_effect迭代器中。这会引发错误,因为模拟ID现在与assert_called_with语句不同。无论如何参考前两个嘲笑来确认他们被称为? – EliSquared