我有这个生产课程:
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,然后返回" text"。
时调用此外,断言失败:
AssertionError: Calls not found.
Expected: [call('str1'), call('str2')]
Actual: []
答案 0 :(得分:1)
这里的问题是您已经嘲笑了Class
,并且没有正确使用您班级的实例。这就是事情不符合预期的原因。
所以,让我们来看看发生了什么。
就在这里:
with patch('fullpath.to.MyClass', autospec=MyClass) as mock:
所以,你在这里做的只是嘲笑你的班级MyClass
。所以,当你这样做时:
mock.set_value.assert_has_calls(calls)
检查执行单元测试时发生的情况,mock
来电实际上会包含此内容:
[call().set_value('str1'), call().foo(), call().set_value('str2')]
请注意call
,因为它写为call()
。 call
参考了您的{{1}}。因此,考虑到这一点,您需要使用名为(在模拟世界的上下文中称为mock
)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
确保您还使用实例模拟对象。根据您最近的异常处理更新,请保留您的try / except而不发表评论。把这一切放在一起,你有:
foo.side_effect