издевательство над функцией, которая принимает **kwargs

У меня проблемы с насмешкой над функцией, которая принимает **kwargs. Сценарий таков: у меня есть ClassA (что в моем конкретном случае я не писал), и у него есть функция с **kwargs. ClassB, который имеет экземпляр ClassA и вызывает функцию **kwargs. Я хочу протестировать ClassB, издеваясь над вызовом функции класса A.

Вот что я пробовал до сих пор, и в обеих моих попытках я получил TypeError. Есть какой-либо способ сделать это? Должен ли я переосмыслить другой аспект этого?

import unittest


#a class i have no control over. Has a function accepting **kwargs
class ClassA(object):

    def classFunctionAcceptingKwargs(self, **kwargs):
        return kwargs["a"] + kwargs["b"]

# a mock of the above class
class Mock_ClassA(object):

    def __init__(self):
        self.mockclassFunctionAcceptingKwargs = lambda **kwargs: None

    def classFunctionAcceptingKwargs(self, **kwargs):
        #FAILS: TypeError: mockFunctionAcceptingKwargs() takes exactly 0 arguments (1 given)
        return self.mockclassFunctionAcceptingKwargs(kwargs)
        #ALSO FAILS: TypeError: mockFunctionAcceptingKwargs() argument after ** must be a mapping, not set
        #return self.mockclassFunctionAcceptingKwargs(**{kwargs["a"] + kwargs["b"]})

#class B calls the class A kwargs but exposes a function with a dict
class ClassB(object):
    def __init__(self, classA):
        self.classA = classA

    def doSomething(self, dict):
        return self.classA.classFunctionAcceptingKwargs(**dict)

class TestClassA(unittest.TestCase):

    def runTest(self):
        a = ClassA()
        result = a.classFunctionAcceptingKwargs(**{"a":1, "b": 2})
        self.assertEqual(result, 3)

class TestClassB(unittest.TestCase):

    def runTest(self):

        mock = Mock_ClassA()
        def mockFunctionAcceptingKwargs(**kwargs):
            self.assertEqual(kwargs["a"], 1)
            self.assertEqual(kwargs["b"], 2)

        mock.mockclassFunctionAcceptingKwargs = mockFunctionAcceptingKwargs
        b = ClassB(mock)
        b.doSomething({"a": 1, "b": 2})

Трассировки стека:

Test Name:  TestClassB
Test Outcome:   Failed
Result StandardError:   
======================================================================
ERROR: runTest (module1.TestClassB)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\test\module1.py", line 49, in runTest
    b.doSomething({"a": 1, "b": 2})
  File "C:\test\module1.py", line 28, in doSomething
    return self.classA.classFunctionAcceptingKwargs(**dict)
  File "C:\test\module1.py", line 18, in classFunctionAcceptingKwargs
    return self.mockclassFunctionAcceptingKwargs(kwargs)
TypeError: mockFunctionAcceptingKwargs() takes exactly 0 arguments (1 given)
----------------------------------------------------------------------
Ran 1 test in 12.873s
FAILED (errors=1)

person Scott Morken    schedule 03.11.2016    source источник


Ответы (1)


Я не уверен, почему вы вызываете другую функцию. Но если вы должны иметь функцию атрибута экземпляра mockclassFunctionAcceptingKwargs, просто передайте словарь kwargs с помощью **kwargs:

class Mock_ClassA(object):
    # ...
    def classFunctionAcceptingKwargs(self, **kwargs):
        return self.mockclassFunctionAcceptingKwargs(**kwargs)

Вам вообще не нужно вызывать эту лямбду, если все, что вам нужно, это чтобы classFunctionAcceptingKwargs существовало:

class Mock_ClassA(object):
    def __init__(self, mock_result):
        self.mock_result = mock_result
    def classFunctionAcceptingKwargs(self, **kwargs):
        self.called_with = kwargs
        return self.mock_result

затем просто передайте издевательское возвращаемое значение, все, что вам нужно передать обратно в ClassB для теста, а затем вы можете проверить, что также было передано правильное значение:

mock = Mock_ClassA(3)  # to return 3 back to the caller
b = ClassB(mock)
b.doSomething({"a": 1, "b": 2})
self.assertEqual(mock.called_with, {'a': 1, 'b': 2})

Вы можете использовать библиотеку unittest.mock для создания макета объекта для передать (доступно в Python 3, и можно установить backport для Python 2). Это позволит вам создать макет ClassA, а затем использовать API, чтобы проверить, использовался ли макет ожидаемым образом:

try:
    # Python 3
    from unittest import mock
except ImportError:
    # Python 2, backport
    import mock

class TestClassB(unittest.TestCase):
    def runTest(self):
        mockA = mock.Mock(spec=ClassA)  # only accept attributes ClassA also has
        mockA.classFunctionAcceptingKwargs.return_value = 3  # or whatever else you want it to return
        b = ClassB(mockA)
        b.doSomething({"a": 1, "b": 2})
        mockA.classFunctionAcceptingKwargs.assert_called_once_with(a=1, b=2)

Демонстрация с unittest.mock в качестве издевательского слоя:

>>> from unittest import mock
>>> class ClassA(object):
...     def classFunctionAcceptingKwargs(self, **kwargs):
...         return kwargs["a"] + kwargs["b"]
...
>>> class ClassB(object):
...     def __init__(self, classA):
...         self.classA = classA
...     def doSomething(self, dict):
...         return self.classA.classFunctionAcceptingKwargs(**dict)
...
>>> mockA = mock.Mock(spec=ClassA)
>>> mockA.classFunctionAcceptingKwargs.return_value = 3
>>> b = ClassB(mockA)
>>> b.doSomething({"a": 1, "b": 2})
3
>>> mockA.classFunctionAcceptingKwargs.assert_called_once_with(a=1, b=2)  # passes, no exception raised
>>> mockA.mock_calls
[call.classFunctionAcceptingKwargs(a=1, b=2)]
person Martijn Pieters    schedule 03.11.2016