Имитация функции для создания исключения для проверки блока except


120

У меня есть функция ( foo), которая вызывает другую функцию ( bar). Если при вызове bar()возникает ошибка HttpError, я хочу обработать это специально, если код состояния равен 404, в противном случае повторно повысить.

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

Вот мой код, который иллюстрирует мою проблему:

import unittest
import mock
from apiclient.errors import HttpError


class FooTests(unittest.TestCase):
    @mock.patch('my_tests.bar')
    def test_foo_shouldReturnResultOfBar_whenBarSucceeds(self, barMock):
        barMock.return_value = True
        result = foo()
        self.assertTrue(result)  # passes

    @mock.patch('my_tests.bar')
    def test_foo_shouldReturnNone_whenBarRaiseHttpError404(self, barMock):
        barMock.side_effect = HttpError(mock.Mock(return_value={'status': 404}), 'not found')
        result = foo()
        self.assertIsNone(result)  # fails, test raises HttpError

    @mock.patch('my_tests.bar')
    def test_foo_shouldRaiseHttpError_whenBarRaiseHttpErrorNot404(self, barMock):
        barMock.side_effect = HttpError(mock.Mock(return_value={'status': 500}), 'error')
        with self.assertRaises(HttpError):  # passes
            foo()

def foo():
    try:
        result = bar()
        return result
    except HttpError as error:
        if error.resp.status == 404:
            print '404 - %s' % error.message
            return None
        raise

def bar():
    raise NotImplementedError()

Я последовал за Mock документов , которые говорят , что вы должны установить side_effectиз Mockэкземпляра к Exceptionклассу иметь Передразнивала функцию поднять ошибку.

Я также посмотрел на некоторые другие связанные вопросы и ответы на StackOverflow, и похоже, что я делаю то же самое, что и они, чтобы вызвать и исключить исключение из их макета.

Почему установив side_effectиз barMockне вызывает ожидаемый Exceptionбыть подняты? Если я делаю что-то странное, как мне протестировать логику в моем exceptблоке?


Я почти уверен, что ваше исключение возникает , но я не уверен, как вы устанавливаете там resp.statusкод. Откуда HTTPErrorвзялось?
Мартейн Питерс

@MartijnPieters HttpErrorэто класс , определенный в Google, apiclientLib , который мы используем в GAE. Он __init__определяется параметрами, (resp, content)поэтому я пытался создать фиктивный экземпляр для ответа с указанием соответствующего кода состояния.
Джесси Уэбб

Правильно, таков этот класс ; но тогда вам не нужен пользователь return_value; respне называется .
Мартейн Питерс

1
Я снова попробовал свой код, не используя, HttpErrorа вместо этого попытался использовать только обычный Exceptionэкземпляр. Это прекрасно работает. Это означает, что это должно иметь какое-то отношение к тому, как я настраиваю HttpErrorэкземпляр, вероятно, связано с тем, как я создаю Mockэкземпляр для ответа.
Джесси Уэбб,

Ответы:


142

Ваш макет просто вызывает исключение, но error.resp.statusзначение отсутствует. Вместо использования return_value, просто скажите, Mockчто statusэто атрибут:

barMock.side_effect = HttpError(mock.Mock(status=404), 'not found')

Дополнительные аргументы ключевого слова для Mock()устанавливаются как атрибуты результирующего объекта.

Я положить ваши fooи barопределения в my_testsмодуле, добавил в HttpErrorклассе , так что я мог бы использовать его тоже, и ваше испытание , то может быть побежал к успеху:

>>> from my_tests import foo, HttpError
>>> import mock
>>> with mock.patch('my_tests.bar') as barMock:
...     barMock.side_effect = HttpError(mock.Mock(status=404), 'not found')
...     result = my_test.foo()
... 
404 - 
>>> result is None
True

Вы даже можете увидеть, как print '404 - %s' % error.messageлиния работает, но я думаю, вы хотели использовать ее error.contentвместо этого; HttpError()во всяком случае, атрибут устанавливается из второго аргумента.


2
side_effectявляется ключевой частью
Daniel Butler
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.