上面的答案很有帮助,但我想要一种简单的方法来编写单元测试,当被测试的代码改变了模拟函数调用的方式时,不需要重构测试代码,而且功能上也没有任何改变。
例如,如果我选择通过关键字部分或完全调用函数(或构建一个kwargs字典并将其插入),而不改变传入的值:
def function_being_mocked(x, y):
pass
def function_being_tested():
function_being_mocked(42, 150)
def function_being_tested():
function_being_mocked(x=42, y=150)
这可能有点过头了,但我希望我的单元测试不必担心函数调用格式的变化,只要预期的值传递给了函数调用(甚至包括指定或不指定默认值)。
以下是我想出的解决方案。希望这能简化您的测试体验:
from inspect import Parameter, Signature, signature
class DefaultValue(object):
def __init__(self, value):
self.value = value
def __eq__(self, other_value) -> bool:
if isinstance(other_value, DefaultValue):
return self.value == other_value.value
return self.value == other_value
def __repr__(self) -> str:
return f'<DEFAULT_VALUE: {self.value}>'
def standardize_func_args(func_sig, args, kwargs, is_method):
kwargs = kwargs.copy()
parameters = list(func_sig.parameters.values())
if is_method:
parameters = list(parameters)[1:]
for (i, arg_value) in enumerate(args):
kwargs[parameters[i].name] = arg_value
kwargs.update({
param.name: DefaultValue(param.default)
for param in parameters
if param.name not in kwargs
})
return {
param.name: kwargs[param.name]
for param in parameters
}
def _validate_func_signature(func_sig: Signature):
assert not any(
p.kind == Parameter.VAR_KEYWORD or p.kind == Parameter.VAR_POSITIONAL
for p in func_sig.parameters.values()
), 'Functions with *args or **kwargs not supported'
def __assert_called_with(mock, func, is_method, *args, **kwargs):
func_sig = signature(func)
_validate_func_signature(func_sig)
mock_args = standardize_func_args(
func_sig, mock.call_args.args, mock.call_args.kwargs, is_method)
func_args = standardize_func_args(func_sig, args, kwargs, is_method)
assert mock_args == func_args, f'Expected {func_args} but got {mock_args}'
def assert_called_with(mock, func, *args, **kwargs):
__assert_called_with(mock, func, False, *args, **kwargs)
def assert_method_called_with(mock, func, *args, **kwargs):
__assert_called_with(mock, func, True, *args, **kwargs)
使用方法:
from unittest.mock import MagicMock
def bar(x, y=5, z=25):
pass
mock = MagicMock()
mock(42)
assert_called_with(mock, bar, 42)
assert_called_with(mock, bar, 42, 5)
assert_called_with(mock, bar, x=42)
assert_called_with(mock, bar, 42, z=25)
assert_called_with(mock, bar, z=25, x=42, y=5)
assert_called_with(mock, bar, 51)
assert_called_with(mock, bar, 42, 51)
在使用之前,请注意一个警告。`assert_called_with()`需要引用原始函数。如果您在单元测试中使用装饰器`@unittest.mock.patch`,可能会出现问题,因为您尝试查找函数签名时可能会选择模拟对象而不是原始函数。
from unittest import mock
class Tester(unittest.TestCase):
@unittest.mock.patch('module.function_to_patch')
def test_function(self, mock):
function_to_be_tested()
assert_called_with(mock, module.function_to_patch, *args, **kwargs)
我建议使用
unittest.mock.patch.object
,这需要你导入被修补的函数,因为我的代码需要引用该函数。
class Tester(unittest.TestCase):
def test_function(self):
orig_func_patched = module.function_to_patch
with unittest.mock.patch.object(module, 'function_to_patch') as mock:
function_to_be_tested()
assert_called_with(mock, orig_func_patched, *args, **kwargs)
lib.event.Event
进行模拟并进行断言。 - vks