2013-11-06 5 views
6

Я тестирую класс, который наследует от другого, очень сложный, с методами соединения с БД и беспорядок зависимостей. Я хотел бы издеваться над своим базовым классом, чтобы я мог хорошо играть с методом, определенным в подклассе, но в тот момент, когда я наследую от издевающегося класса, сам объект превращается в макет и теряет все его методы.Python mock: mocking base class for inheritance

Как я могу высмеять суперкласс?

Более или менее ситуацию можно суммировать в следующем:

import mock 

ClassMock = mock.MagicMock() 

class RealClass(ClassMock): 

    def lol(self): 
     print 'lol' 

real = RealClass() 
real.lol() # Does not print lol, but returns another mock 

print real # prints <MagicMock id='...'> 

Это упрощенный случай. То, что на самом деле происходит, это то, что RealClass расширяет AnotherClass, но мне удалось перехватить AnotherClass и заменить его на макет.

+0

Вам действительно нужен объект Mock в качестве базового класса? Будет ли простой «объект» работать? –

+0

Что такое MagicMock? класс или генератор классов? Обычно вы просто 'class RealClass (mock.MagicMock):' – cmd

ответ

3

Это должно сработать для вас.

import mock 

ClassMock = mock.MagicMock 

class RealClass(ClassMock): 

    def lol(self): 
     print 'lol' 

real = RealClass() 
real.lol() # Does not print lol, but returns another mock 

print real # prints <MagicMock id='...'> 

Вы не должны передавать экземпляр класса так же, как и вы. mock.MagicMock - это класс, поэтому вы передаете его напрямую.

In [2]: inspect.isclass(mock.MagicMock) 
Out[2]: True 
+0

Это не совсем то, что я пытался сделать, но, возможно, это было просто невозможно: я ожидал, что будет макет для базового класса, а затем смогу сказать какие вызовы были сделаны с макетной частью объекта.«Недостатком» этого подхода является то, что подкласс перезаписывает методы MagicMock. В любом случае это работает как шарм. Спасибо – bgusach

+0

У меня возникла новая проблема с этим подходом. Что делать, если я хочу вызвать метод класса «RealClass.do_something», который должен быть определен в базовом классе? поскольку MagicMock не является макетом, а классом, вы не можете добавить к нему магически новые методы: S – bgusach

+1

взял меня на минутку, чтобы выяснить, какая разница здесь. Чтобы помочь другим, разница в ClassMock = mock.MagicMock vs. ClassMock = mock.MagicMock() –

7

Это то, с чем я боролся долгое время, но, думаю, я наконец нашел решение.

Как вы уже заметили, если вы попытаетесь заменить базовый класс на Mock, класс, который вы пытаетесь протестировать, просто превращается в макет, который побеждает вашу способность его тестировать. Решение состоит в том, чтобы высмеять только методы базового класса, а не весь базовый класс, но это проще сказать, чем сделать: это может быть довольно склонным к ошибкам каждый раз по одному методу тестировать на основе теста.

В результате я создал класс, который сканирует другой класс и присваивает себе Mock() s, которые соответствуют методам другого класса. Затем вы можете использовать этот класс вместо реального базового класса при тестировании.

Вот фальшивый класс:

class Fake(object): 
    """Create Mock()ed methods that match another class's methods.""" 

    @classmethod 
    def imitate(cls, *others): 
     for other in others: 
      for name in other.__dict__: 
       try: 
        setattr(cls, name, Mock()) 
       except (TypeError, AttributeError): 
        pass 
     return cls 

Так, например, вы могли бы иметь некоторый код, как это (Извинения это немного надуманный, просто предположим, что BaseClass и SecondClass делают нетривиальную работу и содержать многие методы и даже не обязательно определяется вами на всех):

class BaseClass: 
    def do_expensive_calculation(self): 
     return 5 + 5 

class SecondClass: 
    def do_second_calculation(self): 
     return 2 * 2 

class MyClass(BaseClass, SecondClass): 
    def my_calculation(self): 
     return self.do_expensive_calculation(), self.do_second_calculation() 

Вы бы тогда быть в состоянии написать несколько тестов, как это:

class MyTestCase(unittest.TestCase): 
    def setUp(self): 
     MyClass.__bases__ = (Fake.imitate(BaseClass, SecondBase),) 

    def test_my_methods_only(self): 
     myclass = MyClass() 
     self.assertEqual(myclass.my_calculation(), (
      myclass.do_expensive_calculation.return_value, 
      myclass.do_second_calculation.return_value, 
     )) 
     myclass.do_expensive_calculation.assert_called_once_with() 
     myclass.do_second_calculation.assert_called_once_with() 

Таким образом, методы, существующие на базовых классах, остаются доступными как макеты, с которыми вы можете взаимодействовать, но ваш класс сам по себе не является макетом.

И я был уверен, что это работает как в python2, так и в python3.