Знущання над функцією для підняття винятку для тестування виключно блоку


119

У мене є функція ( 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 Q & As, і схоже, я роблю те саме, що вони роблять, щоб викликати, а Виняток викликати їх знущання.

Чому встановивши side_effectз barMockне викликає очікуваний Exceptionбути підняті? Якщо я роблю щось дивне, як я повинен перейти до тестування логіки в своєму exceptблоці?


Я впевнений , що ваше виключення буде збуджуючись, але я не знаю , як ви встановлюєте resp.statusкод. Звідки береться HTTPError?
Martijn Pieters

@MartijnPieters HttpError- це клас, визначений у lib Google,apiclient який ми використовуємо в GAE. Це __init__визначено парамами, (resp, content)тому я намагався створити екземпляр макети для відповіді із вказаним відповідним кодом статусу.
Джессі Вебб

Так, саме такий клас ; але тоді вам не потрібно користуватися return_value; respне називається .
Martijn Pieters

1
Я спробував свій код ще раз без використання HttpErrorі замість цього спробував використовувати лише звичайний Exceptionекземпляр. Це прекрасно працює. Це означає, що він повинен мати щось спільне з тим, як я конфігурую HttpErrorекземпляр, ймовірно, пов’язаний із тим, як я створюю Mockекземпляр для відповіді.
Джессі Вебб

Відповіді:


141

Ваш макет піднімає виняток просто чудово, але 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.