2014-11-05 76 views
1

我有一个昂贵的__init__函数的类。我不想从测试中调用这个函数。避免执行模拟类的__init__

对于这个例子的目的,我做了一个类,在__init__引发了一个异常:

class ClassWithComplexInit(object): 

    def __init__(self): 
     raise Exception("COMPLEX!") 

    def get_value(self): 
     return 'My value' 

我有构造的ClassWithComplexInit一个实例,并使用它的功能的第二类。

class SystemUnderTest(object): 

    def my_func(self): 
     foo = ClassWithComplexInit() 
     return foo.get_value() 

我想写一些单元测试围绕SystemUnderTest#my_func()。我遇到的问题是无论我如何模拟ClassWithComplexInit__init__函数总是被执行并引发异常。

class TestCaseWithoutSetUp(unittest.TestCase): 

    @mock.patch('mypackage.ClassWithComplexInit.get_value', return_value='test value') 
    def test_with_patched_function(self, mockFunction): 
     sut = SystemUnderTest() 
     result = sut.my_func() # fails, executes ClassWithComplexInit.__init__() 
     self.assertEqual('test value', result) 

    @mock.patch('mypackage.ClassWithComplexInit') 
    def test_with_patched_class(self, mockClass): 
     mockClass.get_value.return_value = 'test value' 
     sut = SystemUnderTest() 
     result = sut.my_func() # seems to not execute ClassWithComplexInit.__init__() 
     self.assertEqual('test value', result) # still fails 
     # AssertionError: 'test value' != <MagicMock name='ClassWithComplexInit().get_value()' id='4436402576'> 

上面的第二种方法是我从this similar Q&A得到的,但它也没有工作。它似乎不运行__init__函数,但我的断言失败,因为结果最终是一个模拟实例,而不是我的价值。

我也试图在setUp功能配置patch例如,使用startstop功能the docs suggest

class TestCaseWithSetUp(unittest.TestCase): 

    def setUp(self): 
     self.mockClass = mock.MagicMock() 
     self.mockClass.get_value.return_value = 'test value' 
     patcher = mock.patch('mypackage.ClassWithComplexInit', self.mockClass) 
     patcher.start() 
     self.addCleanup(patcher.stop) 

    def test_my_func(self): 
     sut = SystemUnderTest() 
     result = sut.my_func() # seems to not execute ClassWithComplexInit.__init__() 
     self.assertEqual('test value', result) # still fails 
     # AssertionError: 'test value' != <MagicMock name='mock().get_value()' id='4554658128'> 

这也似乎在回避我__init__功能,但我为get_value.return_value设置的值不被尊重和get_value()仍返回MagicMock实例。

我该如何模拟一个复杂的__init__类,该类是由我的测试代码实例化的?理想情况下,我希望有一个解决方案适用于TestCase类中的许多单元测试(例如,不需要每次测试都需要patch)。

我正在使用Python版本2.7.6

+0

如何让'ClassWithComplexInit'可用于您的测试脚本? '从mypackage导入ClassWithComplexInit'或其他东西? – chepner 2014-11-05 21:17:48

+0

@chepner我的测试脚本甚至没有导入'ClassWithComplexInit'。我从我的真实代码切换了一堆代码在SO上发布,并搞砸了包引用,我意识到引用是错误的。你在回答中解决了这个问题,谢谢! – 2014-11-05 22:14:34

回答

1

首先,您需要使用相同的名称要修补创建foo,就是

class SystemUnderTest(object): 

    def my_func(self): 
     foo = mypackage.ClassWithComplexInit() 
     return foo.get_value() 

其次,您需要配置正确的模仿对象。您正在配置ClassWithComplexInit.get_value,这是未绑定的方法,但您需要配置ClassWithComplexInit.return_value.get_value,这是Mock对象,实际上将使用foo.get_value()调用该对象。

@mock.patch('mypackage.ClassWithComplexInit') 
def test_with_patched_class(self, mockClass): 
    mockClass.return_value.get_value.return_value = 'test value' 
    sut = SystemUnderTest() 
    result = sut.my_func() # seems to not execute ClassWithComplexInit.__init__() 
    self.assertEqual('test value', result) # still fails 
+0

这工作!我在[嘲笑链接调用的文档]中也遇到了这种方法(http://www.voidspace.org.uk/python/mock/examples.html#mocking-chained-calls)。我正准备自己发布解决方案,但是你打败了我。 – 2014-11-05 22:10:22

+0

此外,表达式'mockClass.return_value.get_value.return_value'可以替换为'mockClass()。get_value.return_value',它仍然有效。 – 2014-11-05 22:11:23

+0

哦,对。模拟不知道它正在填充一个类,所以对“ClassWithComplexInit .__ new__”的每个“调用”将返回相同的对象,并且设置一个对象的get_value()方法的返回值是这样的为他们所有。 – chepner 2014-11-05 22:22:10

-1

为什么不只是将它的子类进行测试并覆盖init?

class testClassWithComplexInit(ClassWithComplexInit): 
    def __init__(self): 
     pass 

ClassWithComplexInit = testClassWithComplexInit 

现在,ClassWithComplexInit().get_value()回报'My value'而不是抛出一个异常。

+0

'__init__'函数不是我想模拟的唯一函数,还有其他复杂的函数,我想用它来模拟。我实际上并不想要使用该类的实例,甚至不需要使用子类,因为那样我就不得不用'pass'来清除它的所有函数。 – 2014-11-05 15:53:50

+0

另外,这个类实例没有被传入,它正在'SystemUnderTest'中被实例化。我将如何使该类使用类的'testClassWithComplexInit'版本? – 2014-11-05 15:56:02