简体   繁体   English

声明对不同模拟对象的一系列调用

[英]Assert a sequence of calls to different mock objects

How can I use the Python mock library to assert a specific sequence of calls to different mock objects? 如何使用Python mock库来声明对不同模拟对象的特定调用序列?

For example, I want to assert: 例如,我要断言:

  • A call of foo(spam, eggs) ; 调用foo(spam, eggs) then 然后
  • A call of bar(beans, ham) ; 称呼bar(beans, ham) then 然后
  • A call of foo(sausage) . 调用foo(sausage)

I can patch each of foo and bar , and the resulting mock objects each allow me to make assertions about calls to that mock. 我可以修补foobar每一个,并且生成的模拟对象每个都允许我对对该模拟的调用进行断言。 But I need to access an overall sequence of calls to make assertions about that sequence. 但是我需要访问整个调用序列以对该序列进行断言。

Yes, ideally I would need only to inspect the resulting state and make assertion about it after the fact. 是的,理想情况下,我只需要检查结果状态并在事实发生后对其进行断言。 But that's not feasible with some systems, and the only workable description of correct state is “these calls were made in this particular sequence”. 但这在某些系统上是不可行的,并且正确状态的唯一可行描述是“这些调用是按此特定顺序进行的”。

What capabilities of the mock library can I use for accessing a sequence of calls to different objects, and asserting the calls were as expected in the right sequence? 我可以使用mock库的哪些功能来访问对不同对象的一系列调用,并断言这些调用符合正确的顺序?

Mock actually provides something like this builtin. 模拟实际上提供了这种内置的东西。 Mocks frequently have a parent mock ... eg 嘲笑经常有一个家长模拟...例如

somemock.foo  # parent is somemock

the parent isn't directly exposed in the mock API, But the calls on the children are registered on the parent. 父级没有直接在模拟API中公开,但是子级上的调用已在父级上注册。

import mock
m = mock.Mock()
m.a('hello world')
m.b('goodbye world')
m.c('kittens!')
m.a('Howdy')
m.assert_has_calls([
  mock.call.a('hello world'),
  mock.call.b('goodbye world'),
  mock.call.c('kittens!'),
  mock.call.a('Howdy')
])  # passes silently
m.assert_has_calls([
  mock.call.a('hello world'),
  mock.call.b('goodbye world'),
  mock.call.a('Howdy'),
  mock.call.c('kittens!')
]) # Error
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "/usr/local/lib/python2.7/dist-packages/mock.py", line 863, in assert_has_calls
#     'Actual: %r' % (calls, self.mock_calls)
# AssertionError: Calls not found.
# Expected: [call.a('hello world'), call.b('goodbye world'), call.a('Howdy'), call.c('kittens!')]
# Actual: [call.a('hello world'),
#  call.b('goodbye world'),
#  call.c('kittens!'),
#  call.a('Howdy')]

But, "My mocks don't all come from the same parent" you might be saying. 但是,您可能会说:“我的模拟并非都来自同一个父母。” All is not yet lost! 一切还没有丢失! You can create a parent and attach them to it after the fact. 您可以创建一个父项并将其附加到事实之后。

parent_mock = mock.Mock()
parent_mock.attach_mock(foomock, 'foo')
parent_mock.attach_mock(barmock, 'bar')

and now you can do the same sort of asserting we did above (so long as you don't need to preserve the original mocks' parents ... then I'm not sure what to tell you...) 现在您可以执行与上述相同的断言(只要您不需要保留原始模拟对象的父母...那么我不确定该告诉您什么...)

An initial attempt at solving this is with a specialised Mock subclass which registers the calls in a provided sequence object, which can be whatever shared sequence you like. 解决此问题的最初尝试是使用专门的Mock子类,该子类将调用注册到提供的序列对象中,该对象可以是您喜欢的任何共享序列。

from copy import deepcopy
import mock

class CallRegisterMock(mock.MagicMock):
    """ A mock object that registers each call. """

    def __init__(self, call_register, *args, **kwargs):
        super(CallRegisterMock, self).__init__(*args, **kwargs)

        self.call_register = call_register

    def __call__(self, *args, **kwargs):
        args = deepcopy(args)
        kwargs = deepcopy(kwargs)
        call = mock.call(*args, **kwargs)
        qualified_call = (self, call)
        self.call_register.append(qualified_call)
        super(CallRegisterMock, self).__call__(*args, **kwargs)

This has drawbacks: 这有缺点:

  • It may be re-inventing one or more wheels. 可能是在重新发明一个或多个轮子。 (Please add a better answer if you think so.) (如果您认为这样,请添加一个更好的答案。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM