断言一个方法是用几个参数调用的

use*_*661 57 python unit-testing

我正在嘲笑requests.post使用该Mock库的电话:

requests.post = Mock()
Run Code Online (Sandbox Code Playgroud)

调用涉及多个参数:URL,有效负载,一些auth等等.我想断言requests.post用特定的URL调用,但我不关心其他参数.当我尝试这个:

requests.post.assert_called_with(requests_arguments)
Run Code Online (Sandbox Code Playgroud)

测试失败,因为它期望仅使用该参数调用它.

有没有办法检查函数调用中某处是否使用了单个参数而不必传入其他参数?

或者,更好的是,有没有办法断言一个特定的URL,然后为其他参数抽象数据类型(即数据应该是字典,auth应该是HTTPBasicAuth的实例,等等)?

k0n*_*0nG 156

您还可以使用ANY帮助程序始终匹配您不知道或不检查的参数.

更多关于ANY的帮助:https: //docs.python.org/3/library/unittest.mock.html#any

所以例如你可以将参数'session'与之类似:

from unittest.mock import ANY
requests_arguments = {'slug': 'foo', 'session': ANY}
requests.post.assert_called_with(requests_arguments)
Run Code Online (Sandbox Code Playgroud)

  • 这实际上应该被接受,因为它完美地运作. (15认同)
  • 旁注:如果您使用 dict 作为输入参数,请确保在 dict 之前包含 **,在本例中为 requests.post.assert_used_with(**requests_arguments) (4认同)
  • 我也使用这个解决方案,但它不提供类型检查。 (2认同)

Bak*_*riu 42

据我所知Mock,没有提供一种方法来实现你想要的东西assert_called_with.您可以访问call_argscall_args_list成员并手动执行断言.

然而,这是一种简单(和肮脏)的方式来实现你想要的几乎.您必须实现一个__eq__方法始终返回的类True:

def Any(cls):
    class Any(cls):
        def __eq__(self, other):
            return True
    return Any()
Run Code Online (Sandbox Code Playgroud)

用它作为:

In [14]: caller = mock.Mock(return_value=None)


In [15]: caller(1,2,3, arg=True)

In [16]: caller.assert_called_with(Any(int), Any(int), Any(int), arg=True)

In [17]: caller.assert_called_with(Any(int), Any(int), Any(int), arg=False)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-17-c604faa06bd0> in <module>()
----> 1 caller.assert_called_with(Any(int), Any(int), Any(int), arg=False)

/usr/lib/python3.3/unittest/mock.py in assert_called_with(_mock_self, *args, **kwargs)
    724         if self.call_args != (args, kwargs):
    725             msg = self._format_mock_failure_message(args, kwargs)
--> 726             raise AssertionError(msg)
    727 
    728 

AssertionError: Expected call: mock(0, 0, 0, arg=False)
Actual call: mock(1, 2, 3, arg=True)
Run Code Online (Sandbox Code Playgroud)

你可以看到它只检查arg.你必须创建子类int,否则比较不会工作1.但是,您仍然必须提供所有参数.如果您有许多参数,可以使用tuple-unpacking缩短代码:

In [18]: caller(1,2,3, arg=True)

In [19]: caller.assert_called_with(*[Any(int)]*3, arg=True)
Run Code Online (Sandbox Code Playgroud)

除此之外,我无法想到一种方法来避免将所有参数传递给您assert_called_with并按照您的意图使用它.


可以扩展上述解决方案以检查其他参数的类型.例如:

In [21]: def Any(cls):
    ...:     class Any(cls):
    ...:         def __eq__(self, other):
    ...:             return isinstance(other, cls)
    ...:     return Any()

In [22]: caller(1, 2.0, "string", {1:1}, [1,2,3])

In [23]: caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(list))

In [24]: caller(1, 2.0, "string", {1:1}, [1,2,3])

In [25]: caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(tuple))
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-25-f607a20dd665> in <module>()
----> 1 caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(tuple))

/usr/lib/python3.3/unittest/mock.py in assert_called_with(_mock_self, *args, **kwargs)
    724         if self.call_args != (args, kwargs):
    725             msg = self._format_mock_failure_message(args, kwargs)
--> 726             raise AssertionError(msg)
    727 
    728 

AssertionError: Expected call: mock(0, 0.0, '', {}, ())
Actual call: mock(1, 2.0, 'string', {1: 1}, [1, 2, 3])
Run Code Online (Sandbox Code Playgroud)

但是这不允许参数可以是例如inta或a str.允许多个参数Any和使用多继承不会有帮助.我们可以解决这个问题abc.ABCMeta

def Any(*cls):
    class Any(metaclass=abc.ABCMeta):
        def __eq__(self, other):
            return isinstance(other, cls)
    for c in cls:
        Any.register(c)
    return Any()
Run Code Online (Sandbox Code Playgroud)

例:

In [41]: caller(1, "ciao")

In [42]: caller.assert_called_with(Any(int, str), Any(int, str))

In [43]: caller("Hello, World!", 2)

In [44]: caller.assert_called_with(Any(int, str), Any(int, str))
Run Code Online (Sandbox Code Playgroud)

1我使用了Any该函数的名称,因为它在代码中"用作类".也是any一个内置...

  • 这是现在内置到框架中(无类型检查)作为另一个答案:/sf/answers/1900641641/ (3认同)
  • 我使用了一个变体,但在较新版本的mock中,他们使用!=作为比较(至少对于kwargs),所以你需要覆盖`def __neq __(self,other):return False`. (2认同)

小智 17

@mock.patch.object(module, 'ClassName')
def test_something(self, mocked):
    do_some_thing()
    args, kwargs = mocked.call_args
    self.assertEqual(expected_url, kwargs.get('url'))
Run Code Online (Sandbox Code Playgroud)

看:call-as-tuples

  • 这个问题的最佳答案 (2认同)

akk*_*kki 7

域名注册地址:

args, kwargs = requests.post.call_args_list[-1]
self.assertTrue('slug' in kwargs, '`slug` not passed to requests.post')
Run Code Online (Sandbox Code Playgroud)

简单来说,我们可以访问一个包含所有位置参数的元组和一个包含所有传递给函数的命名参数的字典——所以现在你可以检查任何你想要的。



我发现这种方法比其他流行的答案更简洁,因为:
如果传递的参数太多,而只检查其中一个,则执行类似操作{'slug': 'foo', 'field1': ANY, 'field2': ANY, 'field3': ANY, ' . . . }可能会很笨拙。


此外,如果您想检查几个字段的数据类型:

args, kwargs = requests.post.call_args_list[0]
self.assertTrue(isinstance(kwargs['data'], dict))
Run Code Online (Sandbox Code Playgroud)

此外,如果您传递参数(而不是关键字参数),您可以通过args如下方式访问它们:

self.assertEqual(
    len(args), 1,
    'post called with different number of arguments than expected'
)
Run Code Online (Sandbox Code Playgroud)

  • 这是一种非常干净的方法,因为您不需要知道传递的参数的数量及其名称。对于 pytest 用户来说,就像 `args, kwargs = requests.post.call_args_list[0]` 然后 `assert 'foo' in kwargs` 和 `assert kwargs['foo'] == 'bar'` 一样简单 (2认同)