Как я непосредственно высмеиваю суперкласс с python mock? - программирование
Подтвердить что ты не робот

Как я непосредственно высмеиваю суперкласс с python mock?

Я использую фреймворк python для тестирования (http://www.voidspace.org.uk/python/mock/), и я хочу издеваться над суперклассом и сосредоточиться на тестировании добавленного поведения подклассов.

(Для тех, кого это касается, у меня есть расширенный pymongo.collection.Collection, и я хочу только проверить свое добавленное поведение. Я не хочу запускать mongodb в качестве другого процесса для целей тестирования.)

Для этого обсуждения A является суперклассом, а B - подклассом. Кроме того, я определяю прямые и косвенные вызовы суперкласса, как показано ниже:

class A(object):
    def method(self):
        ...

    def another_method(self):
        ...

class B(A):
    def direct_superclass_call(self):
        ...
        A.method(self)

    def indirect_superclass_call(self):
        ...
        super(A, self).another_method()

Подход № 1

Определите класс mock для A называется MockA и используйте mock.patch, чтобы заменить его для теста во время выполнения. Это обрабатывает прямые вызовы суперкласса. Затем манипулируйте B.__ base__ для обработки непрямых вызовов суперкласса. (см. ниже)

Возникает проблема в том, что мне приходится писать MockA, а в некоторых случаях (как и в случае pymongo.collection.Collection) это может потребовать много работы, чтобы разгадать все внутренние вызовы, чтобы издеваться.

Подход № 2

Требуемый подход состоит в том, чтобы каким-то образом использовать класс mock.Mock() для обработки вызовов в макете как раз вовремя, так и с определенным значением return_value или side_effect на месте теста. Таким образом, я должен делать меньше работы, избегая определения MockA.

Проблема, с которой я сталкиваюсь, заключается в том, что я не могу понять, как изменить B.__ основания__, чтобы экземпляр mock.Mock() можно было внедрить в качестве суперкласса (мне нужно каким-то образом сделать прямое связывание здесь). До сих пор я определил, что super() проверяет MRO, а затем вызывает первый класс, который определяет данный метод. Я не могу понять, как получить суперкласс для обработки чека и добиться успеха, если он встречается с макетным классом. __getattr__, похоже, не используется в этом случае. Я хочу, чтобы супер подумал, что метод определен в этой точке, а затем использовать функцию mock.Mock(), как обычно.

Как супер() обнаруживает, какие атрибуты определены в классе в последовательности MRO? И есть ли способ для меня вмешаться сюда и каким-то образом заставить его использовать mock.Mock() на лету?

import mock

class A(object):
    def __init__(self, value):
        self.value = value      

    def get_value_direct(self):
        return self.value

    def get_value_indirect(self):
        return self.value   

class B(A):
    def __init__(self, value):
        A.__init__(self, value)

    def get_value_direct(self):
        return A.get_value_direct(self)

    def get_value_indirect(self):
        return super(B, self).get_value_indirect()


# approach 1 - use a defined MockA
class MockA(object):
    def __init__(self, value):
        pass

    def get_value_direct(self):
        return 0

    def get_value_indirect(self):
        return 0

B.__bases__ = (MockA, )  # - mock superclass 
with mock.patch('__main__.A', MockA):  
    b2 = B(7)
    print '\nApproach 1'
    print 'expected result = 0'
    print 'direct =', b2.get_value_direct()
    print 'indirect =', b2.get_value_indirect()
B.__bases__ = (A, )  # - original superclass 


# approach 2 - use mock module to mock out superclass

# what does XXX need to be below to use mock.Mock()?
#B.__bases__ = (XXX, )
with mock.patch('__main__.A') as mymock:  
    b3 = B(7)
    mymock.get_value_direct.return_value = 0
    mymock.get_value_indirect.return_value = 0
    print '\nApproach 2'
    print 'expected result = 0'
    print 'direct =', b3.get_value_direct()
    print 'indirect =', b3.get_value_indirect() # FAILS HERE as the old superclass is called
#B.__bases__ = (A, )  # - original superclass
4b9b3361

Ответ 1

Я играл с издевательством super(), как это было предложено. К сожалению, после больших усилий стало сложно справляться с сложными случаями наследования.

После некоторой работы я понял, что super() обращается к __dict__ классов непосредственно при разрешении атрибутов через MRO (он не выполняет тип вызова getattr). Решение состоит в том, чтобы расширить объект mock.MagicMock() и обернуть его классом для достижения этого. Затем завернутый класс можно поместить в переменную __bases__ подкласса.

Обернутый объект отображает все определенные атрибуты целевого класса в класс __dict__ класса wrapping, так что вызовы super() разрешают правильно исправленные атрибуты внутри внутреннего объекта MagicMock().

Следующий код - это решение, которое я нашел для работы до сих пор. Обратите внимание, что я действительно реализую это в обработчике контекста. Кроме того, следует позаботиться о том, чтобы исправлять в соответствующих пространствах имен при импорте из других модулей.

Это простой пример, иллюстрирующий подход:

from mock import MagicMock
import inspect 


class _WrappedMagicMock(MagicMock):
    def __init__(self, *args, **kwds):
        object.__setattr__(self, '_mockclass_wrapper', None)
        super(_WrappedMagicMock, self).__init__(*args, **kwds)

    def wrap(self, cls):
        # get defined attribtues of spec class that need to be preset
        base_attrs = dir(type('Dummy', (object,), {}))
        attrs = inspect.getmembers(self._spec_class)
        new_attrs = [a[0] for a in attrs if a[0] not in base_attrs]

        # pre set mocks for attributes in the target mock class
        for name in new_attrs:
            setattr(cls, name, getattr(self, name))

        # eat up any attempts to initialize the target mock class
        setattr(cls, '__init__', lambda *args, **kwds: None)

        object.__setattr__(self, '_mockclass_wrapper', cls)

    def unwrap(self):
        object.__setattr__(self, '_mockclass_wrapper', None)

    def __setattr__(self, name, value):
        super(_WrappedMagicMock, self).__setattr__(name, value)

        # be sure to reflect to changes wrapper class if activated
        if self._mockclass_wrapper is not None:
            setattr(self._mockclass_wrapper, name, value)

    def _get_child_mock(self, **kwds):
        # when created children mocks need only be MagicMocks
        return MagicMock(**kwds)


class A(object):
    x = 1

    def __init__(self, value):
        self.value = value

    def get_value_direct(self):
        return self.value

    def get_value_indirect(self):
        return self.value


class B(A):
    def __init__(self, value):
        super(B, self).__init__(value)

    def f(self):
        return 2

    def get_value_direct(self):
        return A.get_value_direct(self)

    def get_value_indirect(self):
        return super(B, self).get_value_indirect()

# nominal behavior
b = B(3)
assert b.get_value_direct() == 3
assert b.get_value_indirect() == 3
assert b.f() == 2
assert b.x == 1

# using mock class
MockClass = type('MockClassWrapper', (), {})
mock = _WrappedMagicMock(A)
mock.wrap(MockClass)

# patch the mock in
B.__bases__ = (MockClass, )
A = MockClass

# set values within the mock
mock.x = 0
mock.get_value_direct.return_value = 0
mock.get_value_indirect.return_value = 0

# mocked behavior
b = B(7)
assert b.get_value_direct() == 0
assert b.get_value_indirect() == 0
assert b.f() == 2
assert b.x == 0

Ответ 2

есть ли способ для меня вставить сюда и каким-то образом заставить его использовать mock.Mock() на лету?

Там могут быть лучшие подходы, но вы всегда можете написать свой собственный super() и ввести его в модуль, содержащий класс, который вы издеваетесь. Пусть он вернет все, что он должен, на основе того, что его вызывает.

Вы можете либо просто определить super() в текущем пространстве имен (в этом случае переопределение применяется только к текущему модулю после определения), или вы можете import __builtin__ и применить переопределение к __builtin__.super, и в этом случае он будет применяться глобально в сеансе Python.

Вы можете захватить оригинальную функцию super (если вам нужно ее вызывать из своей реализации) с использованием аргумента по умолчанию:

def super(type, obj=None, super=super):  
    # inside the function, super refers to the built-in