1. ホーム
  2. python

[解決済み] あるメソッドが複数の引数のうち1つの引数で呼び出されたことを保証する

2022-07-22 06:01:15

質問

への呼び出しをモックにしています。 requests.post への呼び出しをモックしています。 Mock ライブラリを使っています。

requests.post = Mock()

呼び出しには複数の引数が含まれます: URL、ペイロード、いくつかの認証関連などです。私は以下のことを主張したい requests.post が特定の URL で呼び出されたことを主張したいのですが、 他の引数については気にしません。これを試すと

requests.post.assert_called_with(requests_arguments)

の場合、その引数のみで呼ばれることを想定しているため、テストは失敗します。

他の引数を渡すことなく、関数呼び出しのどこかで単一の引数が使用されているかどうかをチェックする方法はありますか?

あるいは、さらに良い方法として、特定の URL をアサートして、他の引数のデータ型を抽象化する方法はありますか (例: data は辞書でなければならない、auth は HTTPBasicAuth のインスタンスでなければならない、など)。

どのように解決するのですか?

私の知る限りでは Mock を介して望むことを実現する方法を提供しません。 assert_called_with . にアクセスすることができます。 call_args call_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])

のような引数は許されません。 int または 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))


1 という名前を使いました。 Any という名前にしました。これは、コード内でクラスとして使用されているからです。また any はビルトインです...