2017-07-19 47 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单元测试陷阱的详细信息: