2017-07-19 54 views
0

考慮下面的代碼(不是一個很好的設計,但是這一點):蟒蛇模擬:更換一個類的方法

class A(object): 
    def __init__(self,filepath): 
     self._access_file_system(filepath) 

    def get(self): 
     return self._result_dict 


class B(object): 
    def __init__(self,filepath1,filepath2): 
     self._filepath1 = filepath1 
     self._filepath2 = filepath2 

    def foo(self): 
     a1 = A(self._filepath1).get() 
     a2 = A(self._filepath2).get() 
     return a1['result']==a2['result'] 

現在,如果我想測試B.foo(),我需要模擬A(如它訪問構造函數內的文件系統)。

編寫測試,將確保B.foo()回報False萬一a1.get()a2.get()提供不同的價值,我也需要模擬B.get()

因此,測試功能或許應該如下所示:

import mock 
mock_get = mock.MagicMock(side_effect=[{'result': 0}, {'result': 1}]) 

@mock.patch('__main__.A') 
def test_foo(MockA): 

    b = B('/file1','/file2') 
    res = b.foo() 
    assert res 
    MockA.assert_any_call('/file1') 
    MockA.assert_any_call('/file2') 

    #Doesn't work - 
    #the assignment doesn't propagate into the objects instantiated inside foo() 
    #A.get = mock_get 

    #The assigned method propagates into the class definition, 
    #so it works - BUT WHY?! 
    a = A(None) 
    a.get = mock_get 

    b = B('/file1', '/file2') 
    res = b.foo() 
    assert not res 

現在,奇怪的一點 - 因爲可以從代碼中的註釋中可以看出,如果我們指定mock_get的類,它贏得不會傳播,但如果我們創建一個實例並分配給它,它就會傳播到該類的其他實例。

我想這種行爲與mock的內部機制有關,所以對我來說理解它是非常重要的,以便正確使用這個庫以及它豐富的功能。

那麼,有沒有人有線索?

回答

1

在第一種情況下,我無法看到您正在修補get方法的任何地方。在調用B之前,您應該將模擬值分配給get方法A。例如,爲什麼下面的測試失敗?:

import mock 
mock_get = mock.MagicMock(side_effect=[{'result': 0}, {'result': 1}]) 

@mock.patch('__main__.A') 
def test_foo(MockA): 

    MockA.get = mock_get 

    b = B('/file1','/file2') 
    res = b.foo() 
    assert not res 
    MockA.assert_any_call('/file1') 
    MockA.assert_any_call('/file2') 

的原因以前的行爲是我們忘記修補的對象(A)的返回值,在這種情況下MockA,而不是對象本身(MockA)。 A對象是實例化A類的結果,您應該訪問A類的return_value的方法。在您的例子將是與此類似:

import mock 
mock_get = mock.MagicMock(side_effect=[{'result': 0}, {'result': 1}]) 

@mock.patch('__main__.A') 
def test_foo(MockA): 

    MockA.return_value.get = mock_get 
    b = B('/file1','/file2') 
    res = b.foo() 

    assert res 
    MockA.assert_any_call('/file1') 
    MockA.assert_any_call('/file2') 

您可以檢查以下一些職位對普通的Python單元測試陷阱的詳細信息: