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

У меня есть функция (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()

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

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

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


person Jesse Webb    schedule 03.02.2015    source источник
comment
Я почти уверен, что ваше исключение возникает, но я не уверен, как вы устанавливаете там код resp.status. Откуда HTTPError?   -  person Martijn Pieters    schedule 03.02.2015
comment
@MartijnPieters HttpError - это класс, определенный в apiclient lib Google, который мы используем в GAE. Он __init__ определяется параметрами (resp, content), поэтому я пытался создать фиктивный экземпляр для ответа с указанием соответствующего кода состояния.   -  person Jesse Webb    schedule 03.02.2015
comment
Верно, это это класс; но тогда вам не нужен пользователь return_value; resp не вызывается.   -  person Martijn Pieters    schedule 03.02.2015
comment
Я снова попробовал свой код, не используя HttpError, а вместо этого попытался использовать обычный Exception экземпляр. Это прекрасно работает. Это означает, что это должно иметь какое-то отношение к тому, как я настраиваю экземпляр HttpError, вероятно, связано с тем, как я создаю экземпляр Mock для ответа.   -  person Jesse Webb    schedule 03.02.2015


Ответы (1)


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

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

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

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

>>> 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(), установленный вторым аргументом, во всяком случае.

person Martijn Pieters    schedule 03.02.2015
comment
side_effect - ключевая часть - person Daniel Butler; 17.04.2019
comment
Что делает HttpError (mock.Mock (status = 404), 'not found')? Для меня он не установил код состояния на 404, он все еще None. - person Yang; 16.11.2020
comment
@Yang: вы используете google-api-client класс исключения? Я не могу помочь вам без каких-либо подробностей о том, что именно вы делаете. Выражение mock.Mock(status=404) создает фиктивный объект с атрибутом .status, который HttpError сохраняет как атрибут resp. В вопросе к нему обращаются как к error.resp.status. - person Martijn Pieters; 16.11.2020