Pytest:使用字典参数断言对函数的多次调用
Pytest: Asserting multiple calls on a function with a dictionary parameter
我想测试一个 python 片段,它从分页的 API 请求信息并在有更多信息可用时更新偏移量。
一些最小的示例代码:
def fetch(url, params):
# call the API
def fetch_all():
params = {"offset": 0}
while True:
result = fetch("example.com", params).json()
if len(result) > 1000: # If there are more items
params["offset"] += 1
else:
break
在测试此函数时,我正在模拟 fetch
函数并检查它是否有多个具有不同偏移量的调用。不幸的是,所有这些调用似乎都引用了同一个字典。
因此,如果我遍历它,例如3次,看起来像这样:
预计:
call("example.com", {"offset": 0}),
call("example.com", {"offset": 1}),
call("example.com", {"offset": 2})
实际:
call("example.com", {"offset": 2}),
call("example.com", {"offset": 2}),
call("example.com", {"offset": 2})
如果我改为将代码更改为 result = fetch("example.com", params.copy())
,它会正常工作。所以模拟中记录的调用肯定似乎引用了同一个字典。
我该如何解决?我真的不想总是为了能够对其进行一些测试而交出副本。
-- 编辑 --
我整理了一个工作示例(称为 test.py
)来显示所述行为:
def fetch(url: str, params: dict):
pass # This is getting mocked
def fetch_all():
params = {"offset": 0}
result = []
while True:
data = fetch("https://example.com", params)
result.extend(data)
if len(data) >= 5:
params["offset"] += 1
else:
break
return result
def test_fetch_all(mocker):
side_effect = [
[1,2,3,4,5],
[6,7]
]
mocked_fetch = mocker.patch("test.fetch", side_effect=side_effect)
assert fetch_all() == [1,2,3,4,5,6,7]
mocked_fetch.assert_has_calls([
mocker.call("https://example.com", params={"offset": 0}),
mocker.call("https://example.com", params={"offset": 1})
])
导致:
AssertionError: Calls not found.
Expected: [call('https://example.com', params={'offset': 0}),
call('https://example.com', params={'offset': 1})]
Actual: [call('https://example.com', {'offset': 1}),
call('https://example.com', {'offset': 1})]
您可以创建存根方法并在每次调用该方法时存储参数的副本,然后针对您存储的副本进行断言。
我对您的工作示例进行了一些修改以使其工作:
def test_fetch_all(mocker):
return_values = [
[1, 2, 3, 4, 5],
[6, 7]
]
call_parameters = []
def my_fetch(url: str, params: dict):
call_parameters.append((url, params.copy()))
return return_values.pop(0)
mocker.patch("test.fetch", side_effect=my_fetch)
assert fetch_all() == [1, 2, 3, 4, 5, 6, 7]
assert call_parameters == [
("https://example.com", {"offset": 0}),
("https://example.com", {"offset": 1})
]
我想测试一个 python 片段,它从分页的 API 请求信息并在有更多信息可用时更新偏移量。
一些最小的示例代码:
def fetch(url, params):
# call the API
def fetch_all():
params = {"offset": 0}
while True:
result = fetch("example.com", params).json()
if len(result) > 1000: # If there are more items
params["offset"] += 1
else:
break
在测试此函数时,我正在模拟 fetch
函数并检查它是否有多个具有不同偏移量的调用。不幸的是,所有这些调用似乎都引用了同一个字典。
因此,如果我遍历它,例如3次,看起来像这样:
预计:
call("example.com", {"offset": 0}),
call("example.com", {"offset": 1}),
call("example.com", {"offset": 2})
实际:
call("example.com", {"offset": 2}),
call("example.com", {"offset": 2}),
call("example.com", {"offset": 2})
如果我改为将代码更改为 result = fetch("example.com", params.copy())
,它会正常工作。所以模拟中记录的调用肯定似乎引用了同一个字典。
我该如何解决?我真的不想总是为了能够对其进行一些测试而交出副本。
-- 编辑 --
我整理了一个工作示例(称为 test.py
)来显示所述行为:
def fetch(url: str, params: dict):
pass # This is getting mocked
def fetch_all():
params = {"offset": 0}
result = []
while True:
data = fetch("https://example.com", params)
result.extend(data)
if len(data) >= 5:
params["offset"] += 1
else:
break
return result
def test_fetch_all(mocker):
side_effect = [
[1,2,3,4,5],
[6,7]
]
mocked_fetch = mocker.patch("test.fetch", side_effect=side_effect)
assert fetch_all() == [1,2,3,4,5,6,7]
mocked_fetch.assert_has_calls([
mocker.call("https://example.com", params={"offset": 0}),
mocker.call("https://example.com", params={"offset": 1})
])
导致:
AssertionError: Calls not found.
Expected: [call('https://example.com', params={'offset': 0}),
call('https://example.com', params={'offset': 1})]
Actual: [call('https://example.com', {'offset': 1}),
call('https://example.com', {'offset': 1})]
您可以创建存根方法并在每次调用该方法时存储参数的副本,然后针对您存储的副本进行断言。
我对您的工作示例进行了一些修改以使其工作:
def test_fetch_all(mocker):
return_values = [
[1, 2, 3, 4, 5],
[6, 7]
]
call_parameters = []
def my_fetch(url: str, params: dict):
call_parameters.append((url, params.copy()))
return return_values.pop(0)
mocker.patch("test.fetch", side_effect=my_fetch)
assert fetch_all() == [1, 2, 3, 4, 5, 6, 7]
assert call_parameters == [
("https://example.com", {"offset": 0}),
("https://example.com", {"offset": 1})
]