根据输入模拟函数内的 API 调用
Mocking an API call within a function based on inputs
假设有一个函数,除其他任务外,还进行了几次 api 调用。有没有办法在测试此功能时模拟所有 api 调用并根据输入从调用中指定 return 值。例如,假设您要测试的函数是这样的:
def someFunction (time, a, b, c) {
const apiReturnA = someApiCall(a)
const returnB = b + 1
const apiReturnC = someApiCall(c)
return [apiReturnA, returnB, apiReturnC]
}
我想测试 someFunction 并指定,每次调用 someApiCall 时,不执行函数,只是 return 基于此函数输入的值。例如,如果我正在处理时间,我希望 api 调用基于特定时间的 return 特定值,否则 return 一个 noop 值。如何做到这一点?
假设您的测试文件是 test.py
,您的库文件是 lib.py
。那么test.py
应该是这样的:
import lib
def fakeApiCall(*args, **kwargs):
return "fake"
lib.someApiCall = fakeApiCall
lib.someFunction(args)
someApiCall
方法只是相关模块命名空间中的一个变量。因此,更改该变量的值。您可能需要深入研究 locals()
and/or globals()
,例如:
data = None
locals()['data'] = "data"
print(data) # will print "data"
您提到 someApiCall
的行为取决于 time
参数:
... lets say that time is some value I care about a specific output from someApiCall, I would want to make sure the mock returns that...
为此,我们必须拦截对外部 someFunction
的调用并检查 time
参数,以便我们可以相应地更新 someApiCall
。一种解决方案是在调用原始 someFunction
.
之前根据 time
参数装饰 someFunction
以拦截调用并在运行时修改 someApiCall
下面是一个使用装饰器的实现。我做了 2 种可能的方法:
- 通过
someFunction_decorator_patch
修补一个
- 和另一个通过手动修改源代码实现,然后通过
someFunction_decorator_reload
执行重新加载
./src.py
from api import someApiCall
def someFunction(time, a, b, c):
apiReturnA = someApiCall(a)
returnB = b + 1
apiReturnC = someApiCall(c)
return [apiReturnA, returnB, apiReturnC]
./api.py
def someApiCall(var):
return var + 2
./test_src.py
from importlib import reload
import sys
import api
from api import someApiCall
from src import someFunction
import pytest
def amend_someApiCall_yesterday(var):
# Reimplement api.someApiCall
return var * 2
def amend_someApiCall_now(var):
# Reimplement api.someApiCall
return var * 3
def amend_someApiCall_later(var):
# Just wrap around api.someApiCall. Call the original function afterwards. Here we can also put
# some conditionals e.g. only call the original someApiCall if the var is an even number.
var *= 4
return someApiCall(var)
def someFunction_decorator_patch(someFunction, mocker):
def wrapper(time, a, b, c):
# If x imports y.z and we want to patch the calls to z, then we have to patch x.z. Patching
# y.z would still retain the original value of x.z thus still calling the original
# functionality. Thus here, we would be patching src.someApiCall and not api.someApiCall.
if time == "yesterday":
mocker.patch("src.someApiCall", side_effect=amend_someApiCall_yesterday)
elif time == "now":
mocker.patch("src.someApiCall", side_effect=amend_someApiCall_now)
elif time == "later":
mocker.patch("src.someApiCall", side_effect=amend_someApiCall_later)
elif time == "tomorrow":
mocker.patch("src.someApiCall", return_value=0)
else:
# Use the original api.someApiCall
pass
return someFunction(time, a, b, c)
return wrapper
def someFunction_decorator_reload(someFunction):
def wrapper(time, a, b, c):
# If x imports y.z and we want to update the functionality of z, then we have to update
# first the functionality of z then reload x. This way, x would have the updated
# functionality of z.
if time == "yesterday":
api.someApiCall = amend_someApiCall_yesterday
elif time == "now":
api.someApiCall = amend_someApiCall_now
elif time == "later":
api.someApiCall = amend_someApiCall_later
elif time == "tomorrow":
api.someApiCall = lambda var: 0
else:
# Use the original api.someApiCall
api.someApiCall = someApiCall
reload(sys.modules['src'])
return someFunction(time, a, b, c)
return wrapper
@pytest.mark.parametrize(
'time',
[
'yesterday',
'now',
'later',
'tomorrow',
'whenever',
],
)
def test_sample(time, mocker):
a, b, c = 10, 10, 10
someFunction_wrapped_patch = someFunction_decorator_patch(someFunction, mocker)
result_1 = someFunction_wrapped_patch(time, a, b, c)
print("Using patch:", time, result_1)
someFunction_wrapped_reload = someFunction_decorator_reload(someFunction)
result_2 = someFunction_wrapped_reload(time, a, b, c)
print("Using reload:", time, result_2)
输出:
$ pytest -rP
____________________________________________________________________________________ test_sample[yesterday] _____________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: yesterday [20, 11, 20]
Using reload: yesterday [20, 11, 20]
_______________________________________________________________________________________ test_sample[now] ________________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: now [30, 11, 30]
Using reload: now [30, 11, 30]
______________________________________________________________________________________ test_sample[later] _______________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: later [42, 11, 42]
Using reload: later [42, 11, 42]
_____________________________________________________________________________________ test_sample[tomorrow] _____________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: tomorrow [0, 11, 0]
Using reload: tomorrow [0, 11, 0]
_____________________________________________________________________________________ test_sample[whenever] _____________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: whenever [12, 11, 12]
Using reload: whenever [12, 11, 12]
======================================================================================= 5 passed in 0.03s =======================================================================================
在这里,您可以看到来自 someApiCall
的响应根据 time
参数发生变化。
yesterday
表示 var * 2
now
表示 var * 3
later
表示 (var * 4) + 2
tomorrow
表示 0
- 任何其他表示 var + 2 的默认实现
假设有一个函数,除其他任务外,还进行了几次 api 调用。有没有办法在测试此功能时模拟所有 api 调用并根据输入从调用中指定 return 值。例如,假设您要测试的函数是这样的:
def someFunction (time, a, b, c) {
const apiReturnA = someApiCall(a)
const returnB = b + 1
const apiReturnC = someApiCall(c)
return [apiReturnA, returnB, apiReturnC]
}
我想测试 someFunction 并指定,每次调用 someApiCall 时,不执行函数,只是 return 基于此函数输入的值。例如,如果我正在处理时间,我希望 api 调用基于特定时间的 return 特定值,否则 return 一个 noop 值。如何做到这一点?
假设您的测试文件是 test.py
,您的库文件是 lib.py
。那么test.py
应该是这样的:
import lib
def fakeApiCall(*args, **kwargs):
return "fake"
lib.someApiCall = fakeApiCall
lib.someFunction(args)
someApiCall
方法只是相关模块命名空间中的一个变量。因此,更改该变量的值。您可能需要深入研究 locals()
and/or globals()
,例如:
data = None
locals()['data'] = "data"
print(data) # will print "data"
您提到 someApiCall
的行为取决于 time
参数:
... lets say that time is some value I care about a specific output from someApiCall, I would want to make sure the mock returns that...
为此,我们必须拦截对外部 someFunction
的调用并检查 time
参数,以便我们可以相应地更新 someApiCall
。一种解决方案是在调用原始 someFunction
.
time
参数装饰 someFunction
以拦截调用并在运行时修改 someApiCall
下面是一个使用装饰器的实现。我做了 2 种可能的方法:
- 通过
someFunction_decorator_patch
修补一个
- 和另一个通过手动修改源代码实现,然后通过
someFunction_decorator_reload
执行重新加载
./src.py
from api import someApiCall
def someFunction(time, a, b, c):
apiReturnA = someApiCall(a)
returnB = b + 1
apiReturnC = someApiCall(c)
return [apiReturnA, returnB, apiReturnC]
./api.py
def someApiCall(var):
return var + 2
./test_src.py
from importlib import reload
import sys
import api
from api import someApiCall
from src import someFunction
import pytest
def amend_someApiCall_yesterday(var):
# Reimplement api.someApiCall
return var * 2
def amend_someApiCall_now(var):
# Reimplement api.someApiCall
return var * 3
def amend_someApiCall_later(var):
# Just wrap around api.someApiCall. Call the original function afterwards. Here we can also put
# some conditionals e.g. only call the original someApiCall if the var is an even number.
var *= 4
return someApiCall(var)
def someFunction_decorator_patch(someFunction, mocker):
def wrapper(time, a, b, c):
# If x imports y.z and we want to patch the calls to z, then we have to patch x.z. Patching
# y.z would still retain the original value of x.z thus still calling the original
# functionality. Thus here, we would be patching src.someApiCall and not api.someApiCall.
if time == "yesterday":
mocker.patch("src.someApiCall", side_effect=amend_someApiCall_yesterday)
elif time == "now":
mocker.patch("src.someApiCall", side_effect=amend_someApiCall_now)
elif time == "later":
mocker.patch("src.someApiCall", side_effect=amend_someApiCall_later)
elif time == "tomorrow":
mocker.patch("src.someApiCall", return_value=0)
else:
# Use the original api.someApiCall
pass
return someFunction(time, a, b, c)
return wrapper
def someFunction_decorator_reload(someFunction):
def wrapper(time, a, b, c):
# If x imports y.z and we want to update the functionality of z, then we have to update
# first the functionality of z then reload x. This way, x would have the updated
# functionality of z.
if time == "yesterday":
api.someApiCall = amend_someApiCall_yesterday
elif time == "now":
api.someApiCall = amend_someApiCall_now
elif time == "later":
api.someApiCall = amend_someApiCall_later
elif time == "tomorrow":
api.someApiCall = lambda var: 0
else:
# Use the original api.someApiCall
api.someApiCall = someApiCall
reload(sys.modules['src'])
return someFunction(time, a, b, c)
return wrapper
@pytest.mark.parametrize(
'time',
[
'yesterday',
'now',
'later',
'tomorrow',
'whenever',
],
)
def test_sample(time, mocker):
a, b, c = 10, 10, 10
someFunction_wrapped_patch = someFunction_decorator_patch(someFunction, mocker)
result_1 = someFunction_wrapped_patch(time, a, b, c)
print("Using patch:", time, result_1)
someFunction_wrapped_reload = someFunction_decorator_reload(someFunction)
result_2 = someFunction_wrapped_reload(time, a, b, c)
print("Using reload:", time, result_2)
输出:
$ pytest -rP
____________________________________________________________________________________ test_sample[yesterday] _____________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: yesterday [20, 11, 20]
Using reload: yesterday [20, 11, 20]
_______________________________________________________________________________________ test_sample[now] ________________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: now [30, 11, 30]
Using reload: now [30, 11, 30]
______________________________________________________________________________________ test_sample[later] _______________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: later [42, 11, 42]
Using reload: later [42, 11, 42]
_____________________________________________________________________________________ test_sample[tomorrow] _____________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: tomorrow [0, 11, 0]
Using reload: tomorrow [0, 11, 0]
_____________________________________________________________________________________ test_sample[whenever] _____________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: whenever [12, 11, 12]
Using reload: whenever [12, 11, 12]
======================================================================================= 5 passed in 0.03s =======================================================================================
在这里,您可以看到来自 someApiCall
的响应根据 time
参数发生变化。
yesterday
表示 var * 2now
表示 var * 3later
表示 (var * 4) + 2tomorrow
表示 0- 任何其他表示 var + 2 的默认实现