入力引数に基づくPython関数のモック


150

しばらくの間、Pythonで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」を返します

これはpythonのmockitoで利用できるようです

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

私はモックで同じことを達成する方法を探していましたが、成功しませんでしたか?

何か案は?


2
-この答えは役立つかもしれstackoverflow.com/a/7665754/234606
naiquevin

@naiquevinこれは問題の仲間を完全に解決します、ありがとう!
ファンアントニオゴメスモリアーノ2013

MocktioをPythonで使用できるとは思いませんでした。
ベン

プロジェクトでpytestを使用している場合は、そのような目的でを活用することができますmonkeypatch。Monkeypatchは、「テストのためにこの関数を置き換える」ためのものですが、Mockは、mock_callsそれを確認したり、呼び出されたものについてアサーションを作成したりする場合にも使用します。両方の場所があり、私は多くの場合、特定のテストファイルで異なる時間に両方を使用します。
ドリフトキャッチャー2018年

回答:


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関数の名前を別の名前に変更できますか?(私は知っている、私は知っている、それはかなり単純ですが、関数名とパラメーター名が異なるという事実を読みやすさを向上させます:)
Juan Antonio Gomez Moriano 2013

6
@JuanAntonioGomezMorianoできますが、この場合はドキュメントを直接引用しているだけなので、特に壊れていなければ引用を編集するのは少し苦手です。
アンバー

そして、これらすべての年のすべての後にside effect
見慣れる

7
@Ishはの名前について不平を言っていませんCallableMixin.side_effectが、例で定義されている個別の関数は同じ名前を持っています。
OrangeDog 2018年

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
これにより、選択した回答よりも明確になりました。自分の質問に回答していただきありがとうございます:)
Luca Bezerra '10

15

副作用は関数(ラムダ関数の場合もあります)を取るため、単純なケースでは次のように使用できます。

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

4

私はここで「入力引数に基づいて関数をモックする方法」を探していて、これを解決して簡単なaux関数を作成しました。

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

パラメータを取る関数を使用したいが、モックしている関数は使用partialfunctoolsない場合にも、from を使用できます。例えばこのように:

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

これは(Djangoの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.pypandasデータベースからの読み取りに使用していて、モック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

「入力パラメータが特定の値のときに固定値を返したい」場合、モックは必要なくdictgetメソッドとともにを使用できます。

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

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

これは、偽の出力が入力のマッピングである場合にうまく機能します。それが入力の関数であるとき、私はアンバーの回答side_effectに従って使用することをお勧めします。

あなたが保存したい場合は、両方の組み合わせを使用することができるMockの能力(assert_called_oncecall_countなど):

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

これはとても賢いです。
エミラー

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