2014-11-05 2 views
1

У меня есть класс с дорогой функцией __init__. Я не хочу, чтобы эта функция вызывалась из тестов.Избегайте выполнения __init__ из издевающегося класса

Для этого примера я сделал класс, который вызывает исключение в __init__:

class ClassWithComplexInit(object): 

    def __init__(self): 
     raise Exception("COMPLEX!") 

    def get_value(self): 
     return 'My value' 

У меня есть второй класс, который создает экземпляр ClassWithComplexInit и использует его функции.

class SystemUnderTest(object): 

    def my_func(self): 
     foo = ClassWithComplexInit() 
     return foo.get_value() 

Я пытаюсь написать несколько модульных тестов вокруг SystemUnderTest#my_func(). Проблема, с которой я сталкиваюсь, неважно, как я пытаюсь высмеять ClassWithComplexInit, функция __init__ всегда выполняется и исключение возникает.

class TestCaseWithoutSetUp(unittest.TestCase): 

    @mock.patch('mypackage.ClassWithComplexInit.get_value', return_value='test value') 
    def test_with_patched_function(self, mockFunction): 
     sut = SystemUnderTest() 
     result = sut.my_func() # fails, executes ClassWithComplexInit.__init__() 
     self.assertEqual('test value', result) 

    @mock.patch('mypackage.ClassWithComplexInit') 
    def test_with_patched_class(self, mockClass): 
     mockClass.get_value.return_value = 'test value' 
     sut = SystemUnderTest() 
     result = sut.my_func() # seems to not execute ClassWithComplexInit.__init__() 
     self.assertEqual('test value', result) # still fails 
     # AssertionError: 'test value' != <MagicMock name='ClassWithComplexInit().get_value()' id='4436402576'> 

Второй подход выше является тот, который я получил от this similar Q&A, но это не сработало. Это показалось, чтобы не запускать функцию __init__, но мое утверждение терпит неудачу, потому что результат заканчивается тем, что является экземпляром mock, а не моим значением.

Я также попытался настроить patch экземпляр в функции setUp, используя start и stop функции, как the docs suggest.

class TestCaseWithSetUp(unittest.TestCase): 

    def setUp(self): 
     self.mockClass = mock.MagicMock() 
     self.mockClass.get_value.return_value = 'test value' 
     patcher = mock.patch('mypackage.ClassWithComplexInit', self.mockClass) 
     patcher.start() 
     self.addCleanup(patcher.stop) 

    def test_my_func(self): 
     sut = SystemUnderTest() 
     result = sut.my_func() # seems to not execute ClassWithComplexInit.__init__() 
     self.assertEqual('test value', result) # still fails 
     # AssertionError: 'test value' != <MagicMock name='mock().get_value()' id='4554658128'> 

Это также, кажется, чтобы избежать моей __init__ функции, но значение я устанавливаю для get_value.return_value не соблюдается и get_value() по-прежнему возвращает MagicMock экземпляр.

Как я могу издеваться над классом со сложным __init__, который был создан моим тестируемым кодом? В идеале я хотел бы получить решение, которое хорошо подходит для многих модульных тестов в классе TestCase (например, не нужно до patch каждый тест).

Я использую версию Python 2.7.6.

+0

Как вы можете использовать ClassWithComplexInit для вашего тестового скрипта? 'from mypackage import ClassWithComplexInit' или что-то еще? – chepner

+0

@chepner Мой тестовый скрипт даже не импортировал 'ClassWithComplexInit'. Я переключил кучу кода с моего реального кода, чтобы опубликовать здесь на SO и испортил ссылки на пакеты, я понимаю, что ссылка неверна.Вы обращаетесь к этому в своем ответе, хотя, спасибо! –

ответ

1

Во-первых, вам нужно использовать то же имя вы заплат для создания foo, то есть

class SystemUnderTest(object): 

    def my_func(self): 
     foo = mypackage.ClassWithComplexInit() 
     return foo.get_value() 

Во-вторых, вам нужно настроить правильный фиктивный объект. Вы настраиваете ClassWithComplexInit.get_value, метод unbound, но вам нужно настроить ClassWithComplexInit.return_value.get_value, который является объектом Mock, который на самом деле будет вызван с помощью foo.get_value().

@mock.patch('mypackage.ClassWithComplexInit') 
def test_with_patched_class(self, mockClass): 
    mockClass.return_value.get_value.return_value = 'test value' 
    sut = SystemUnderTest() 
    result = sut.my_func() # seems to not execute ClassWithComplexInit.__init__() 
    self.assertEqual('test value', result) # still fails 
+0

Это сработало! Я также наткнулся на этот подход в [документах для издевательских цепочек] (http://www.voidspace.org.uk/python/mock/examples.html#mocking-chained-calls). Я как раз собирался опубликовать решение самостоятельно, но вы избили меня до него. –

+0

Кроме того, выражение 'mockClass.return_value.get_value.return_value' может быть заменено на' mockClass(). Get_value.return_value' и оно по-прежнему работает. –

+0

О, право. Макет не знает, что он заполняется для класса, поэтому каждый «вызов» класса «ClassWithComplexInit .__ new__» будет возвращать один и тот же объект, а установка возвращаемого значения метода 'get_value()' этого объекта делает это для всех. – chepner

-1

Почему бы не просто подклассировать его для теста и перезаписать init?

class testClassWithComplexInit(ClassWithComplexInit): 
    def __init__(self): 
     pass 

ClassWithComplexInit = testClassWithComplexInit 

Теперь ClassWithComplexInit().get_value() возвращается 'My value' вместо повышения исключение.

+0

Функция '__init__' - не единственная функция, которую я пытаюсь высмеять, есть и другие сложные функции, для которых я хочу использовать mock. Я действительно не хочу, чтобы экземпляр класса использовался, даже не подкласс, потому что тогда мне пришлось бы исчерпывать все его функции с помощью 'pass'. –

+0

Кроме того, этот экземпляр класса не передается, он создается в 'SystemUnderTest'. Как я могу заставить этот класс использовать версию класса testClassWithComplexInit? –

Смежные вопросы