【问题标题】:Python3 mock: assert_has_calls for production-code methods?Python3 模拟:用于生产代码方法的 assert_has_calls?
【发布时间】:2016-04-02 17:06:56
【问题描述】:

我有这个生产类:

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 方法)。有什么办法让我告诉 mock 使用实际的方法,以便我以后可以阅读它的调用方法?

附:我知道我可以将“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)

但我看到模拟没有成功,因为当我希望它首先引发 MyError,然后返回“text”时调用真正的“foo”。

另外,断言失败:

AssertionError: Calls not found.
Expected: [call('str1'), call('str2')]
Actual: []

【问题讨论】:

  • 您是否阅读过模拟和单元测试文档?这绝对是可行的。 SO上有几个关于这个的例子。您是否有任何可以共享的单元测试代码来帮助您解决遇到的困难?
  • 我仍然打开标签查看它,但我看到的只是对模拟的“调用”的调用。如果我使用“with patch.object(MyClass, 'set_value') as mock_fetch:”,mock_fetch 将......好吧,替换我的方法。
  • 这不是很有帮助。请修改您的问题并提供您的单元测试代码的minimal reproducible example
  • 但是如果你在测试Caller,你就不是在测试MyClass。你确定你不能完全模拟MyClass吗?
  • 这可能是个好主意...我对python很陌生,对python的模拟也很陌生...如果我只为MyClass使用MagicMock,那么我应该能够调用 my_magic_mock.set_value.assert_has_calls()。这应该可以清除我的测试代码中的许多“with patch(...)”-es。

标签: python python-3.x mocking


【解决方案1】:

这里的问题是你已经模拟了你的Class,并且没有正确使用你的类的instance。这就是为什么事情没有按预期运行的原因。

那么,让我们来看看发生了什么。

就在这里:

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 是指您的mock 这里。因此,考虑到这一点,您需要使用 称为(在模拟世界的上下文中也称为 return_valuemock 来正确引用您尝试测试的模拟对象。解决此问题的快速方法是使用mock()。所以你只需要改变这个:

mock().set_value.assert_has_calls(calls)

但是,为了更明确地说明您在做什么,您可以声明您实际上正在使用调用mock 的结果。此外,最好注意使用更明确的名称,而不是mock。试试MyClassMock,然后你将实例命名为my_class_mock_obj

my_class_mock_obj = MyClassMock.return_value

因此,在您的单元测试中,更明确的是您使用的是类的模拟对象。此外,最好在进行方法调用之前设置所有模拟,并且对于您的foo.side_effect,确保您也在使用 instance 模拟对象。根据您最近对异常处理的更新,请保持您的 try/except 没有 cmets。综上所述,您有:

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)

【讨论】:

  • 你试过它是否有效?首先,“mock.foo.side_effect =”对我没有影响。其次, assert_has_calls 对我来说仍然失败。可能是“MyClassMock.return_value”实际上在此处实例化一个对象,这是调用者实例化的不同对象。
  • 完全测试。这样可行。试试看。但这里重要的是理解为什么你的实现不起作用。清楚吗? @Feoggou
  • @Feoggou 您在问题中提供的代码是否完全相同?因为我几乎复制了你的样本并让它工作。如果您有不同的代码,那么您不能在这里真正指责我的答案。此外,它为什么不起作用的基本概念在答案中。因此,如果您有不同的代码,请使用我的解释作为指导来弄清楚发生了什么,或者更新您的答案以使事情更清楚。
  • 您的单元测试甚至没有测试mock.foo.side_effect = [MyError("msg"), "text"] 发生的事情。此外,在您提供的示例中,您只调用了一次foo
  • 我已经复制了你在这里展示的这个测试示例(除了我重命名了“mock.foo.side_effect”->“MyClassMock.foo.side_effect”。其他类是一样的。应该有引发异常 MyError。
猜你喜欢
  • 1970-01-01
  • 2011-01-21
  • 1970-01-01
  • 2013-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多