Python mock - 修改类属性的模拟类方法

run*_*box 5 python unit-testing mocking python-unittest.mock pytest-mock

我目前想要测试以下基本 Python 类:

class Example:

    def run_steps(self):
        self.steps = 0

        while self.steps < 4:
            self.step()
    
    def step(self):
        # some expensive API call
        print("wasting time...")
        time.sleep(1000)

        self.steps += 1
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,step() 方法包含一个昂贵的 API 调用,因此我想用另一个函数来模拟它,该函数可以避免昂贵的 API 调用,但仍然会递增self.steps。我发现这样做是可能的(从这里可以看出):

def mock_step(self):
    print("skip the wasting time")
    self.steps += 1

# This code works!
def test(mocker):
    example = Example()
    mocker.patch.object(Example, 'step', mock_step)

    example.run_steps()
Run Code Online (Sandbox Code Playgroud)

我只是创建一个名为 的函数mock_step(self)来避免 API 调用,然后step()用新mock_step(self)函数修补原来的慢速方法。

然而,这导致了新的问题。由于该mock_step(self)函数不是 Mock 对象,因此我无法调用其上的任何 Mock 方法(例如 assert_used() 和 call_count()):

def test(mocker):
    example = Example()
    mocker.patch.object(Example, 'step', mock_step)

    example.run_steps()

    # this line doesn't work
    assert mock_step.call_count == 4
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,我尝试mock_step使用以下参数包装 Mock 对象wraps

def test(mocker):
    example = Example()

    # this doesn't work
    step = mocker.Mock(wraps=mock_step)
    mocker.patch.object(Example, 'step', step)

    example.run_steps()

    assert step.call_count == 4
Run Code Online (Sandbox Code Playgroud)

但后来我得到了一个不同的错误说mock_step() missing 1 required positional argument: 'self'.

因此,从这个阶段开始,我不确定如何断言step()run_steps().

MrB*_*men 2

有几种解决方案,最简单的可能是使用具有副作用的标准模拟:

def mock_step(self):
    print("skip the wasting time")
    self.steps += 1


def test_step(mocker):
    example = Example()
    mocked = mocker.patch.object(Example, 'step')
    mocked.side_effect = lambda: mock_step(example)
    example.run_steps()
    assert mocked.call_count == 4
Run Code Online (Sandbox Code Playgroud)

side_effect可以接受可调用的,因此您可以使用标准模拟和修补方法。