python mock:替换类方法

时间:2017-07-19 09:45:40

标签: python unit-testing mocking

考虑以下代码(不是一个非常好的设计,但重点):

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的内部机制有关,因此对我来说理解它很重要,要正确使用这个库及其丰富的功能。< / p>

那么,有没有人有线索?

1 个答案:

答案 0 :(得分:1)

在第一种情况下,我无法看到您正在修补get方法的任何地方。在调用get之前,您应该将模拟值分配给A B方法。例如,为什么以下测试失败?:

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类的结果,您应该访问return_valueA的方法。在您的示例中,它将类似于此:

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单元测试陷阱的更多信息: