web-dev-qa-db-ja.com

Pythonメソッドが複数回呼び出されたモックオブジェクト

私がテストしているクラスは、依存関係として別のクラス(CUTのinitメソッドに渡されるインスタンス)を持っています。 Python Mockライブラリを使用して、このクラスをモックアウトします。

私が持っているものは次のようなものです:

mockobj = Mock(spec=MyDependencyClass)
mockobj.methodfromdepclass.return_value = "the value I want the mock to return"
assertTrue(mockobj.methodfromdepclass(42), "the value I want the mock to return")

cutobj = ClassUnderTest(mockobj)

これは問題ありませんが、「methodfromdepclass」はパラメータ化されたメソッドであるため、methodfromdepclassに渡される引数に応じて異なる値を返す単一のモックオブジェクトを作成します。

このパラメーター化された動作が必要な理由は、異なる値(mockobjから返される値によって生成される値)を含むClassUnderTestの複数のインスタンスを作成することです。

ちょっと私が考えていること(これはもちろん機能しません):

mockobj = Mock(spec=MyDependencyClass)
mockobj.methodfromdepclass.ifcalledwith(42).return_value = "you called me with arg 42"
mockobj.methodfromdepclass.ifcalledwith(99).return_value = "you called me with arg 99"

assertTrue(mockobj.methodfromdepclass(42), "you called me with arg 42")
assertTrue(mockobj.methodfromdepclass(99), "you called me with arg 99")

cutinst1 = ClassUnderTest(mockobj, 42)
cutinst2 = ClassUnderTest(mockobj, 99)

# now cutinst1 & cutinst2 contain different values

この "ifcalledwith"のセマンティクスをどのように実現しますか?

47
Adam Parkin

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
74
k.parnell

少し甘い:

mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}[x]

または複数の引数の場合:

mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}[x]

またはデフォルト値で:

mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}.get(x, 20000)

または両方の組み合わせ:

mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}.get(x, 20000)

そして陽気に私たちは行きます。

48
abourget

私は自分でテストをしていたときにこれに遭遇しました。 methodfromdepclass()への呼び出しをキャプチャすることを気にせず、何かを返すためにそれを必要とするだけであれば、以下で十分です。

def makeFakeMethod(mapping={}):
    def fakeMethod(inputParam):
        return mapping[inputParam] if inputParam in mapping else MagicMock()
    return fakeMethod

mapping = {42:"Called with 42", 59:"Called with 59"}
mockobj.methodfromdepclass = makeFakeMethod(mapping)

パラメーター化されたバージョンは次のとおりです。

def makeFakeMethod():
    def fakeMethod(param):
        return "Called with " + str(param)
    return fakeMethod
10
Addison

here の場合と同様に、side_effect in nittest.mock.Mock を使用することもできます @mock.patch.object with new_callable。これにより、オブジェクトの属性にモックオブジェクトをパッチできます。

モジュール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