Python mock - 模拟 class 修改 class 属性的方法
Python mock - mocking class method that modifies class attributes
我目前有以下要测试的基本 Python class:
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
.我发现这样做是可能的(从 可以看出):
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 调用,并且我用新的 mock_step(self)
函数修补了原来的慢速 step()
方法。
但是,这会导致新的问题。由于 mock_step(self)
函数不是 Mock 对象,我无法调用它的任何 Mock 方法(例如 assert_called() 和 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
为了解决这个问题,我尝试使用 wraps
参数用 Mock 对象包装 mock_step
:
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 次。
对此有多种解决方案,最简单的可能是使用具有副作用的标准模拟:
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
可以接受一个可调用对象,因此您可以同时使用标准模拟和修补方法。
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 次。
我目前有以下要测试的基本 Python class:
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
.我发现这样做是可能的(从
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 调用,并且我用新的 mock_step(self)
函数修补了原来的慢速 step()
方法。
但是,这会导致新的问题。由于 mock_step(self)
函数不是 Mock 对象,我无法调用它的任何 Mock 方法(例如 assert_called() 和 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
为了解决这个问题,我尝试使用 wraps
参数用 Mock 对象包装 mock_step
:
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 次。
对此有多种解决方案,最简单的可能是使用具有副作用的标准模拟:
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
可以接受一个可调用对象,因此您可以同时使用标准模拟和修补方法。
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 次。