web-dev-qa-db-ja.com

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

しばらくの間、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")

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

何か案は?

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

152
Amber

メソッドが複数回呼び出された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 kwarg['foo'] == 7:
        return "Foo is seven"

mockobj.mockmethod.side_effect = my_side_effect

それはトリックです

副作用には関数(lambda functionの場合もあります)がかかるため、単純な場合には次のように使用できます。

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

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

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
Manu

別の方法を示すためだけに:

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
caleb

@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`
0