입력 인수를 기반으로 모의 파이썬 함수


150

우리는 파이썬을 위해 Mock 을 한동안 사용 하고 있습니다.

이제 우리는 함수를 조롱하려는 상황이 있습니다

def foo(self, my_param):
    #do something here, assign something to my_result
    return my_result

일반적으로 이것을 조롱하는 방법은 다음과 같습니다 (foo가 객체의 일부라고 가정)

self.foo = MagicMock(return_value="mocked!")

심지어 foo ()를 몇 번 호출하면 사용할 수 있습니다.

self.foo = MagicMock(side_effect=["mocked once", "mocked twice!"])

이제 입력 매개 변수에 특정 값이있을 때 고정 값을 반환하려는 상황에 직면하고 있습니다. "my_param"이 "something"과 같다면 "my_cool_mock"을 반환하고 싶습니다

이것은 파이썬을 위해 mockito에서 사용할 수있는 것 같습니다

when(dummy).foo("something").thenReturn("my_cool_mock")

나는 성공하지 못한 채 Mock으로 동일한 것을 달성하는 방법을 찾고 있습니까?

어떤 아이디어?


2
-이 답변이 도움이 될 수 있음 stackoverflow.com/a/7665754/234606
naiquevin

@naiquevin 이것은 문제 배우자를 완벽하게 해결합니다. 감사합니다!
Juan Antonio Gomez Moriano

나는 Mocktio를 Python과 함께 사용할 수 있다는 것을 몰랐다.

프로젝트가 pytest를 사용하는 경우 이러한 목적으로를 활용하는 것이 좋습니다 monkeypatch. Monkeypatch는 "테스트를 위해이 기능을 대체"하는 데 도움이되는 반면 Mock은 mock_calls호출 된 항목 등 을 확인 하거나 어설 션 할 때 사용 합니다. 둘 다를위한 장소가 있으며 주어진 테스트 파일에서 다른 시간에 두 가지를 자주 사용합니다.
driftcatcher

답변:


187

side_effect함수 인 경우 해당 함수가 리턴하는 것은 모의 리턴을 호출하는 것입니다. 이 side_effect함수는 모형과 동일한 인수로 호출됩니다. 이를 통해 입력에 따라 호출의 반환 값을 동적으로 변경할 수 있습니다.

>>> def side_effect(value):
...     return value + 1
...
>>> m = MagicMock(side_effect=side_effect)
>>> m(1)
2
>>> m(2)
3
>>> m.mock_calls
[call(1), call(2)]

http://www.voidspace.org.uk/python/mock/mock.html#calling


25
답을 더 쉽게하기 위해 side_effect 함수의 이름을 다른 것으로 바꿀 수 있습니까? (나는, 나 알고있다 알고있다, 그것은 꽤 간단하지만 가독성을 함수 이름 및 매개 변수 이름은 : 다른 사실 향상
후안 안토니오 고메즈 Moriano

6
@ JuanAntonioGomezMoriano 할 수는 있지만이 경우 문서를 직접 인용하기 때문에 인용 부호가 구체적으로 손상되지 않은 경우 편집하는 것이 다소 불편합니다.
Amber

그리고 몇 년이 지난 지금까지도 독창적 이었지만 side effect정확한 용어는 다음과 같습니다. en.wikipedia.org/wiki/Side_effect_(computer_science)
lsh

7
@Ish는의 이름에 대해 불평하지 CallableMixin.side_effect않지만 예제에 정의 된 별도의 함수는 동일한 이름을 가지고 있습니다.
OrangeDog

48

여러 번 호출 된 메소드Python Mock 객체에 표시된 것처럼

해결책은 내 자신의 side_effect를 작성하는 것입니다

def my_side_effect(*args, **kwargs):
    if args[0] == 42:
        return "Called with 42"
    elif args[0] == 43:
        return "Called with 43"
    elif kwargs['foo'] == 7:
        return "Foo is seven"

mockobj.mockmethod.side_effect = my_side_effect

그 트릭을 수행


2
이렇게하면 선택한 답변보다 더 명확 해
졌으므로

15

부작용은 함수 ( 람다 함수 일 수도 있음)를 취 하므로 간단한 경우에는 다음을 사용할 수 있습니다.

m = MagicMock(side_effect=(lambda x: x+1))

4

나는 여기서 " 입력 인수를 기반으로 함수를 모의하는 방법 "을 찾고 결국 간단한 보조 함수를 만들어 이것을 해결했다.

def mock_responses(responses, default_response=None):
  return lambda input: responses[input] if input in responses else default_response

지금:

my_mock.foo.side_effect = mock_responses(
  {
    'x': 42, 
    'y': [1,2,3]
  })
my_mock.goo.side_effect = mock_responses(
  {
    'hello': 'world'
  }, 
  default_response='hi')
...

my_mock.foo('x') # => 42
my_mock.foo('y') # => [1,2,3]
my_mock.foo('unknown') # => None

my_mock.goo('hello') # => 'world'
my_mock.goo('ey') # => 'hi'

이것이 누군가를 도울 수 있기를 바랍니다!


2

당신은 또한 사용할 수 있습니다 partial에서 functools당신이 매개 변수를 사용하는 함수를 사용하려면하지만 당신은 조롱하는 기능은하지 않습니다. 예를 들면 다음과 같습니다.

def mock_year(year):
    return datetime.datetime(year, 11, 28, tzinfo=timezone.utc)
@patch('django.utils.timezone.now', side_effect=partial(mock_year, year=2020))

이것은 장고의 timezone.now ()와 같은 매개 변수를 허용하지 않는 호출 가능 항목을 반환하지만 내 mock_year 함수는 수행합니다.


이 우아한 솔루션에 감사드립니다. 원래 함수에 추가 매개 변수가있는 경우 프로덕션 코드에서 키워드로 호출해야하며 그렇지 않으면이 방법이 작동하지 않습니다. 오류가 발생했습니다 : got multiple values for argument.
Erik Kalkoken

1

다른 방법을 보여주기 위해 :

def mock_isdir(path):
    return path in ['/var/log', '/var/log/apache2', '/var/log/tomcat']

with mock.patch('os.path.isdir') as os_path_isdir:
    os_path_isdir.side_effect = mock_isdir

1

당신은 또한 사용할 수 있습니다 @mock.patch.object:

모듈 이 데이터베이스에서 읽는 my_module.py데 사용 한다고 가정 하고 인수 를 취하는 pandas모의 pd.read_sql_table방법 으로이 모듈을 테스트하려고 table_name합니다.

당신이 할 수있는 일은 db_mock제공된 인수에 따라 다른 객체를 반환 하는 메소드를 테스트 내에서 작성하는 것입니다.

def db_mock(**kwargs):
    if kwargs['table_name'] == 'table_1':
        # return some DataFrame
    elif kwargs['table_name'] == 'table_2':
        # return some other DataFrame

테스트 기능에서 다음을 수행하십시오.

import my_module as my_module_imported

@mock.patch.object(my_module_imported.pd, "read_sql_table", new_callable=lambda: db_mock)
def test_my_module(mock_read_sql_table):
    # You can now test any methods from `my_module`, e.g. `foo` and any call this 
    # method does to `read_sql_table` will be mocked by `db_mock`, e.g.
    ret = my_module_imported.foo(table_name='table_1')
    # `ret` is some DataFrame returned by `db_mock`

1

이 경우 "입력 매개 변수에 특정의 값이 포함되어있는 경우 고정 된 값을 반환하려면" , 어쩌면 당신도 모의가 필요하지 않습니다 및 사용할 수 dict의와 함께 get방법 :

foo = {'input1': 'value1', 'input2': 'value2'}.get

foo('input1')  # value1
foo('input2')  # value2

이것은 가짜 출력이 입력 매핑 일 때 잘 작동합니다 . 그것은 때 기능 의 입력이 내가 사용하는 것이 좋습니다 것 side_effect에 따라 황색 의 대답.

Mock의 기능 ( assert_called_once, call_count등) 을 유지하려는 경우 두 가지 조합을 사용할 수도 있습니다 .

self.mock.side_effect = {'input1': 'value1', 'input2': 'value2'}.get

이것은 매우 영리합니다.
emyller

-6

나는 꽤 오래된 질문을 알고 있습니다 .python lamdba를 사용하여 개선하는 데 도움이 될 수 있습니다

self.some_service.foo.side_effect = lambda *args:"Called with 42" \
            if args[0] == 42 \
            else "Called with 42" if args[0] == 43 \
            else "Called with 43" if args[0] == 43 \
            else "Called with 45" if args[0] == 45 \
            else "Called with 49" if args[0] == 49 else None
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.