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

问题描述 投票:0回答:3

我目前有以下要测试的基本 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

如您所见,step() 方法包含昂贵的 API 调用,因此我想用另一个函数来模拟它,该函数可以避免昂贵的 API 调用,但仍会递增

self.steps
。我发现这样做是可能的(从here可见):

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()

我简单地创建了一个名为

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

为了解决这个问题,我尝试使用

mock_step
参数将
wraps
与 Mock 对象包装在一起:

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

但随后我收到了一个不同的错误,说

mock_step() missing 1 required positional argument: 'self'

因此,从这个阶段开始,我不确定如何断言

step()
run_steps()
中被调用了 4 次。

python unit-testing mocking python-unittest.mock pytest-mock
3个回答
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

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


0
投票
import unittest.mock as mock
from functools import partial


def fake_step(self):
    print("faked")
    self.steps += 1


def test_api():
    api = Example()
    with mock.patch.object(api, attribute="step", new=partial(fake_step, self=api)):
        # we need to use `partial` to emulate that a real method has its `self` parameter bound at instantiation
        api.run_steps()
    assert api.steps == 4

正确输出

"faked"
4 次。


0
投票

已解决:

不需要创建self,self通过get描述符自然传递

这就是我们如何创建不需要预先创建 self 的 MagicMock:

from unittest.mock import MagicMock


class MethodMock(MagicMock):

    __get_mock__ = None

    def __get__(self, instance, owner):

        if self.__get_mock__ is None:
            self.__get_mock__ = MagicMock(side_effect=self.side_effect.__get__(instance, owner))
        return self.__get_mock__


def test(mocker):

    example = Example()
    mocker.patch.object(Example, 'step', new_callable=MethodMock, side_effect=mock_step)

    example.run_steps()
    assert example.steps == 4
    assert example.step.call_count == 4
© www.soinside.com 2019 - 2024. All rights reserved.