2013-10-06 3 views
2

Когда я вызываю mock.patch, я ожидаю, что он заменит тип. Я заменяю тип, который я предоставил, используя новый аргумент ключевого слова.
It does not replace the type, но он возвращает правильный объект, когда вызывается patch.start().
FakesPatcher - это хак, который заставляет старый объект создавать новый объект. Он работает для Python 3.x и PyPy. Однако it doesn't work for Python 2.x. (см. Ниже).
Я хочу, чтобы FakesPatcher ушел в любом случае и вместо этого использовал mock.patch.Патч не заменяет целевой объект, но возвращает его правильно

Что я делаю неправильно здесь и как я могу это исправить?

def substitute(obj, qualified_name, spec): 
    testdouble = mock.patch(qualified_name, spec=spec, spec_set=True, new=obj) 
    testdouble.attribute_name = qualified_name # Forces patch to use the extra patcher 

    class FakesPatcher(object): 
     """Ugly hack.""" 
     new = 1 

     def _new(*args, **kwargs): 
      return obj.__new__(obj) 

     def __enter__(self): 
      self._old_new = spec.__new__ 
      spec.__new__ = self._new 
      return obj 

     def __exit__(self, exc_type, exc_val, exc_tb): 
      spec.__new__ = self._old_new 

    testdouble.additional_patchers.append(FakesPatcher()) 

    return testdouble 


def fake(obj): 
    """ 


    :rtype : mock._patch 
    :param obj: 
    """ 
    try: 
     configuration = obj.Configuration() 
    except AttributeError: 
     raise TypeError('A fake testdouble must have a Configuration class.') 

    try: 
     spec = configuration.spec 
    except AttributeError: 
     raise TestDoubleConfigurationError('The type to be faked was not specified.') 

    qualified_name = get_qualified_name(spec) 

    attrs = dict(obj.__dict__) 
    attrs.pop('Configuration') 

    methods = get_missing_methods(spec, obj) 
    for method in methods: 
     def make_default_implementation(attr): 
      def default_implementation(*args, **kwargs): 
       raise NotImplementedError('%s was not implemented when the object was faked.' % attr) 

      return default_implementation 

     attrs.update({method: make_default_implementation(method)}) 

    properties = get_missing_properties(spec, obj) 
    for prop in properties: 
     def make_default_implementation(attr): 
      def default_implementation(*args, **kwargs): 
       raise NotImplementedError('%s was not implemented when the object was faked.' % attr) 

      return property(fget=lambda *args, **kwargs: default_implementation(*args, **kwargs), 
          fset=lambda *args, **kwargs: default_implementation(*args, **kwargs), 
          fdel=lambda *args, **kwargs: default_implementation(*args, **kwargs)) 

     attrs.update({prop: make_default_implementation(prop)}) 

    fake_qualified_name = get_qualified_name(obj) 
    obj = type(obj.__name__, obj.__bases__, attrs) 

    return substitute(obj, qualified_name, spec) 

В случае, если вы хотите играть с кодом и проверить его, вы можете найти его here.

EDIT:
I solved ошибки Python 2.x путем замены лямбда методом экземпляра.

ответ

1

В ваших тестах, если вы хотите использовать mock.patch в заявлении with, в библиотеке макета требуется, чтобы вы использовали возвращаемое значение патча как макет объекта. Ваш тест сейчас

@it.should('replace the original methods with the fake methods') 
    def test_should_replace_the_original_methods_with_the_fake_methods(case): 
     class FakeObject(object): 
      class Configuration(object): 
       spec = RealObject 

      def was_faked(self): 
       return True 

     with fake(FakeObject) as realObject: 
      fake_obj = realObject() 

      case.assertTrue(fake_obj.was_faked()) 

Вы можете использовать следующую замену или даже избавиться от него.

def substitute(obj, qualified_name, spec): 
    return mock.patch(qualified_name, new=obj, spec=spec) 

Работы по исправлению, исправляющие типы на вызывающем сайте. Важна следующая выдержка из документации.

Цель должна быть строкой в ​​виде 'package.module.ClassName'. Целевой объект импортируется, и указанный объект заменяется новым объектом , поэтому цель должна быть импортируемой из среды, в которой вы указываете . Цель импортируется, когда оформленная функция выполнена, а не во время украшения.

Если вы хотите, чтобы исправить фактический тип, без использования возвращаемого значения с with заявления, вы не должны разрешать имя класса для квалифицированного имени, но местное название.

Следующие изменения

@it.should('replace the original methods with the fake methods') 
    def test_should_replace_the_original_methods_with_the_fake_methods(case): 
     ... 
    with fake(FakeObject, '%s.%s' % (__name__,'RealObject')): 
     fake_obj = RealObject() 

     case.assertTrue(fake_obj.was_faked()) 

testdoubles__init__.py

def fake(obj, qualified_name=None): 
    """ 
    :rtype : mock._patch 
    :param obj: 
    """ 
    try: 
     configuration = obj.Configuration() 
    except AttributeError: 
     raise TypeError('A fake testdouble must have a Configuration class.') 

    try: 
     spec = configuration.spec 
    except AttributeError: 
     raise TestDoubleConfigurationError('The type to be faked was not specified.') 

    qualified_name = qualified_name or get_qualified_name(spec) 
    ... 

Теперь проблема заключается в том, что вы не можете надежно узнать, где RealObject исходит из, по крайней мере, я не мог действительно найти способ. Вы можете предположить, что это из модуля, в котором находится вызывающая функция:

qualified_name = "%s.%s" % (obj.__module__, spec.__name__) 
+0

Что делать, если я заменяю его по всему миру? –