programing tip

여러 인수 중 하나를 사용하여 메서드가 호출되었음을 확인

itbloger 2021. 1. 9. 09:35
반응형

여러 인수 중 하나를 사용하여 메서드가 호출되었음을 확인


라이브러리 requests.post사용에 대한 호출을 조롱 하고 Mock있습니다.

requests.post = Mock()

호출에는 URL, 페이로드, 일부 인증 항목 등 여러 인수가 포함됩니다 requests.post. 특정 URL로 호출되는 것을 주장하고 싶지만 다른 인수는 신경 쓰지 않습니다. 내가 이것을 시도 할 때 :

requests.post.assert_called_with(requests_arguments)

테스트는 해당 인수로만 호출 될 것으로 예상하므로 실패합니다.

다른 인수를 전달하지 않고도 함수 호출의 어딘가에 단일 인수가 사용되는지 확인할 수있는 방법이 있습니까?

또는 더 좋은 방법은 특정 URL을 주장한 다음 다른 인수에 대한 데이터 유형을 추상화하는 방법이 있습니까 (예 : 데이터는 사전이어야하고 인증은 HTTPBasicAuth의 인스턴스 여야 함)?


내가 아는 한을 Mock통해 원하는 것을 달성하는 방법을 제공하지 않습니다 assert_called_with. call_argscall_args_list멤버에 액세스 하고 어설 션을 수동으로 수행 할 수 있습니다.

그러나 이것은 당신이 원하는 것을 거의 달성하는 간단하고 더러운 방법입니다 . __eq__메서드가 항상 반환 하는 클래스를 구현해야 합니다 True.

def Any(cls):
    class Any(cls):
        def __eq__(self, other):
            return True
    return Any()

다음과 같이 사용 :

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)

보시다시피 arg. 의 하위 클래스를 만들어야합니다 int. 그렇지 않으면 비교가 작동하지 않습니다 1 . 그러나 여전히 모든 인수를 제공해야합니다. 인수가 많으면 튜플 풀기를 사용하여 코드를 줄일 수 있습니다.

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

In [19]: caller.assert_called_with(*[Any(int)]*3, arg=True)

이것을 제외하고는 모든 매개 변수를 전달하는 것을 피하고 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])

그러나 이것은 예를 들어 an int또는 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()

예:

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))

1Any 함수 의 이름 은 코드에서 "클래스로 사용"되었기 때문에 사용했습니다. 또한 any내장 ...


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)

@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'))

참조 : 튜플로 호출


If there are too many parameters being passed and only one of them is to be checked, doing something like {'slug': 'foo', 'field1': ANY, 'field2': ANY, 'field3': ANY, ' . . . } can be clumsy.


I took the following approach to achieve this:

args, kwargs = requests.post.call_args_list[0]
self.assertTrue('slug' in kwargs, 'Slug not passed to requests.post')

In simple words, this returns a tuple with all positional arguments and dictionary with all named arguments passed to the function call, so now you can check anything you want.


Furthermore, if you wanted to check the datatype of a few fields

args, kwargs = requests.post.call_args_list[0]
self.assertTrue((isinstance(kwargs['data'], dict))


Also, if you're passing arguments (instead of keyword arguments), you can access them via args like this

self.assertEqual(
    len(args), 1,
    'post called with different number of arguments than expected'
)

You can use : assert_any_call(args) https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_any_call

requests.post.assert_any_call(requests_arguments)

ReferenceURL : https://stackoverflow.com/questions/21611559/assert-that-a-method-was-called-with-one-argument-out-of-several

반응형