2014-11-05 75 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