Python3 模拟:assert_has_calls 用于生产代码方法?
Python3 mock: assert_has_calls for production-code methods?
我有这个作品class:
class MyClass:
def __init__(self):
self.value = None
def set_value(self, value):
self.value = value
def foo(self):
# work with self.value here
# raise RuntimeError("error!")
return "a"
正在从另一个地方使用,像这样:
class Caller:
def bar(self, smth):
obj = MyClass()
obj.set_value(smth)
# ...
# try:
obj.foo()
# except MyError:
# pass
obj.set_value("str2")
# obj.foo()
我得到了这个:
class MyError(Exception):
pass
在我的测试中,我想确保 Caller.bar 调用 obj.set_value,首先使用 smth="a",然后使用 smth="b",但我想要它真正设置值(即调用真正的 set_value 方法)。有什么办法让我告诉模拟使用实际方法,以便我稍后可以阅读它调用的内容吗?
P.S。我知道我可以更改 "foo" 以要求参数 "smth" 这样我就可以摆脱 "set_value",但我想知道是否还有其他选择。
好的,所以我在测试中尝试了这个:
def test_caller(self):
with patch('fullpath.to.MyClass', autospec=MyClass) as mock:
mock.foo.side_effect = [MyError("msg"), "text"]
caller = Caller()
caller.bar("str1")
calls = [call("str1"), call("str2")]
mock.set_value.assert_has_calls(calls)
但是我看到模拟没有成功,因为真正的 "foo" 在我希望它首先引发 MyError 时被调用,然后是 return "text".
此外,断言失败:
AssertionError: Calls not found.
Expected: [call('str1'), call('str2')]
Actual: []
这里的问题是您模拟了 Class
,并且没有正确使用 class 的 实例 。这就是为什么事情没有按预期运行的原因。
所以,让我们来看看发生了什么。
就在这里:
with patch('fullpath.to.MyClass', autospec=MyClass) as mock:
所以,您在这里所做的只是模拟您的 class MyClass
。所以,当你这样做时:
mock.set_value.assert_has_calls(calls)
并检查执行单元测试时发生了什么,您的 mock
调用实际上将包含以下内容:
[call().set_value('str1'), call().foo(), call().set_value('str2')]
注意call
写成call()
。 call
是参考你的 mock
这里。因此,考虑到这一点,您需要使用 called(在模拟世界的上下文中又名 return_value
)mock
来正确引用您的模拟对象正在尝试测试。解决这个问题的快速方法是简单地使用 mock()
。所以你只需要更改为:
mock().set_value.assert_has_calls(calls)
但是,为了更明确地说明您在做什么,您可以声明您实际上正在使用调用 mock
的结果。此外,实际上最好注意使用更明确的名称,而不是 mock
。尝试 MyClassMock
,这又将您的实例命名为 my_class_mock_obj
:
my_class_mock_obj = MyClassMock.return_value
因此,在您的单元测试中,更明确的是您正在使用 class 的模拟对象。此外,最好在进行方法调用之前设置所有模拟,并且对于 foo.side_effect
确保您也在使用 instance 模拟对象。根据您最近对异常处理的更新,请保留您的 try/except 不加评论。将所有这些放在一起,您有:
def test_caller(self):
with patch('tests.test_dummy.MyClass', autospec=MyClass) as MyClassMock:
my_class_mock_obj = MyClassMock.return_value
my_class_mock_obj.foo.side_effect = [MyError("msg"), "text"]
caller = Caller()
caller.bar("str1")
calls = [call("str1"), call("str2")]
my_class_mock_obj.set_value.assert_has_calls(calls)
我有这个作品class:
class MyClass:
def __init__(self):
self.value = None
def set_value(self, value):
self.value = value
def foo(self):
# work with self.value here
# raise RuntimeError("error!")
return "a"
正在从另一个地方使用,像这样:
class Caller:
def bar(self, smth):
obj = MyClass()
obj.set_value(smth)
# ...
# try:
obj.foo()
# except MyError:
# pass
obj.set_value("str2")
# obj.foo()
我得到了这个:
class MyError(Exception):
pass
在我的测试中,我想确保 Caller.bar 调用 obj.set_value,首先使用 smth="a",然后使用 smth="b",但我想要它真正设置值(即调用真正的 set_value 方法)。有什么办法让我告诉模拟使用实际方法,以便我稍后可以阅读它调用的内容吗?
P.S。我知道我可以更改 "foo" 以要求参数 "smth" 这样我就可以摆脱 "set_value",但我想知道是否还有其他选择。
好的,所以我在测试中尝试了这个:
def test_caller(self):
with patch('fullpath.to.MyClass', autospec=MyClass) as mock:
mock.foo.side_effect = [MyError("msg"), "text"]
caller = Caller()
caller.bar("str1")
calls = [call("str1"), call("str2")]
mock.set_value.assert_has_calls(calls)
但是我看到模拟没有成功,因为真正的 "foo" 在我希望它首先引发 MyError 时被调用,然后是 return "text".
此外,断言失败:
AssertionError: Calls not found.
Expected: [call('str1'), call('str2')]
Actual: []
这里的问题是您模拟了 Class
,并且没有正确使用 class 的 实例 。这就是为什么事情没有按预期运行的原因。
所以,让我们来看看发生了什么。
就在这里:
with patch('fullpath.to.MyClass', autospec=MyClass) as mock:
所以,您在这里所做的只是模拟您的 class MyClass
。所以,当你这样做时:
mock.set_value.assert_has_calls(calls)
并检查执行单元测试时发生了什么,您的 mock
调用实际上将包含以下内容:
[call().set_value('str1'), call().foo(), call().set_value('str2')]
注意call
写成call()
。 call
是参考你的 mock
这里。因此,考虑到这一点,您需要使用 called(在模拟世界的上下文中又名 return_value
)mock
来正确引用您的模拟对象正在尝试测试。解决这个问题的快速方法是简单地使用 mock()
。所以你只需要更改为:
mock().set_value.assert_has_calls(calls)
但是,为了更明确地说明您在做什么,您可以声明您实际上正在使用调用 mock
的结果。此外,实际上最好注意使用更明确的名称,而不是 mock
。尝试 MyClassMock
,这又将您的实例命名为 my_class_mock_obj
:
my_class_mock_obj = MyClassMock.return_value
因此,在您的单元测试中,更明确的是您正在使用 class 的模拟对象。此外,最好在进行方法调用之前设置所有模拟,并且对于 foo.side_effect
确保您也在使用 instance 模拟对象。根据您最近对异常处理的更新,请保留您的 try/except 不加评论。将所有这些放在一起,您有:
def test_caller(self):
with patch('tests.test_dummy.MyClass', autospec=MyClass) as MyClassMock:
my_class_mock_obj = MyClassMock.return_value
my_class_mock_obj.foo.side_effect = [MyError("msg"), "text"]
caller = Caller()
caller.bar("str1")
calls = [call("str1"), call("str2")]
my_class_mock_obj.set_value.assert_has_calls(calls)