Переопределение декоратора во время модульного теста в python

У меня есть представление, основанное на классе django, которое я украшаю. К сожалению, этот декоратор делает внешние вызовы для проверки состояния, что выходит за рамки того, что должен делать модульный тест, поэтому я хочу переопределить декоратор, чтобы он ничего не делал во время моих модульных тестов. Вот мой декоратор:

декораторы.py

def status_check(func):
    @wraps(func)
    def wrapped(request, *args, **kwargs):

        uri = settings.SERVER_URI
        status_code = None
        bad_status = [404, 500]

        try:
            response = requests.head(uri)
        except requests.ConnectionError as err:
            LOGGER.error('Server is hosed! Oh Noes! Error: %s ' % (err))
            raise Http404
        except Exception as err:
            LOGGER.error('Some crazy stuff is happening. Its Bad. '
                         'Error: %s' % (err))
            raise Http404

        status_code = response.status_code
        if not status_code or status_code in bad_status:
            LOGGER.error('Awww Snap! Server is not happy: %s' % (status_code))
            raise Http404
        return func(request, *args, **kwargs)
    return wrapped

просмотры.py

class HandleFoo(DetailView):
    @method_decorator(status_check)
    def post(self, request):
        # do stuff here

тесты.py

class RunTest(TestCase):
    def test_post(self):
        post_data = json.dumps({'stuff': 'vodka', 'things': 'tonic'})
        resp = self.client.post(self.foo_uri,
                                post_data,
                                content_type='application/json',
                               )
        self.assertEqual(resp.status_code, 200)

Итак, есть ли способ переопределить декоратор или я могу вообще обойти его? Я довольно озадачен этим.

EDIT Попытался смоделировать запрос, используя следующее от krak3n:

@patch('app.views.method_decorator.status_check', lambda func: func)
@patch('app.views.status_check', lambda func: func)
@patch('app.decorators.status_check', lambda func: func)
@patch('app.views.HandleFoo.post', lambda func: func)

Последний метод подходит мне ближе всего, но в итоге он выдает трассировку стека:

Traceback (most recent call last):
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/mock.py", line 1201, in patched
    return func(*args, **keywargs)
  File "/Users/squiddly/projects/tests/app/tests.py", line 165, in test_post
    content_type='application/json',
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/client.py", line 463, in post
    response = super(Client, self).post(path, data=data, content_type=content_type, **extra)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/client.py", line 297, in post
    return self.request(**r)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/client.py", line 406, in request
    response = self.handler(environ)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/client.py", line 111, in __call__
    response = self.get_response(request)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/core/handlers/base.py", line 178, in get_response
    response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/core/handlers/base.py", line 224, in handle_uncaught_exception
    return callback(request, **param_dict)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/utils/decorators.py", line 91, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/views/defaults.py", line 41, in server_error
    return http.HttpResponseServerError(template.render(Context({})))
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 140, in render
    return self._render(context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/utils.py", line 65, in instrumented_test_render
    return self.nodelist.render(context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 830, in render
    bit = self.render_node(node, context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/debug.py", line 74, in render_node
    return node.render(context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/loader_tags.py", line 124, in render
    return compiled_parent._render(context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/utils.py", line 65, in instrumented_test_render
    return self.nodelist.render(context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 830, in render
    bit = self.render_node(node, context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/debug.py", line 74, in render_node
    return node.render(context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/loader_tags.py", line 156, in render
    return self.render_template(self.template, context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/loader_tags.py", line 138, in render_template
    output = template.render(context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 140, in render
    return self._render(context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/test/utils.py", line 65, in instrumented_test_render
    return self.nodelist.render(context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 830, in render
    bit = self.render_node(node, context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/debug.py", line 74, in render_node
    return node.render(context)
  File "/Users/squiddly/envs/testenv/lib/python2.7/site-packages/django/template/base.py", line 1185, in render
    _dict = func(*resolved_args, **resolved_kwargs)
  File "/Users/squiddly/projects/tests/app/templatetags/app_extras.py", line 40, in get_data
    if request.session.has_key('start_time'):
AttributeError: 'str' object has no attribute 'session'

person John P. Neumann    schedule 29.03.2013    source источник


Ответы (1)


Я думаю, вам придется попасть в темный подземный мир Mock, но как только вы освоите его (если вы еще этого не сделали), темный подземный мир превратится в ярко-синее небесное небо насмешки.

Вы можете использовать модуль patch из Mock для исправления этого декоратора, чтобы ваши представления, использующие его, могли стать более проверяемыми: http://www.voidspace.org.uk/python/mock/patch.html. Лично я раньше не пробовал издеваться над декоратором, но он должен работать...

@patch('python.path.to.decorator', new_callable=PropertyMock)
def my_test(self, decorator_mock):
    # your test code

Дайте этому вихрь.

Вы можете прочитать о модуле patch в Mock здесь: http://www.voidspace.org.uk/python/mock/patch.html

редактировать

new_callable=PropertyMock, вероятно, не подходит для исправления декоратора.

Возможно, попробуйте:

@patch('python.path.to.decorator', lambda: func: func)
def my_test(self):
    # your test code

Теоретически это должно исправить декоратор, чтобы он просто возвращал функцию обратно, а не делал все, что у вас есть в wrapped.

person krak3n    schedule 29.03.2013
comment
Спасибо за помощь. Пробовали оба метода, но они все равно вызывают декоратор: @patch('app.decorators.status_check', lambda func: func) и @patch('app.decorators.status_check', new_callable=PropertyMock) - person John P. Neumann; 29.03.2013
comment
Все в порядке, я уверен, что был бы способ сделать это. Я, вероятно, не дал правильный код :( Этот ответ, вероятно, более полезен: stackoverflow.com/questions/7667567/ - person krak3n; 30.03.2013
comment
Я пробовал это решение перед публикацией, но оно не сработало. :/ Я думаю, что это должно быть сочетание двух, но как этого добиться, я не уверен. Сегодня попробую еще раз. Спасибо за помощь. Очень признателен. - person John P. Neumann; 02.04.2013
comment
После попытки сделать эту работу в течение нескольких дней я, наконец, понял, почему она не работает. Когда вы используете from django.core.urlresolvers import reverse, а затем используйте reverse('все, что указывает на представления'), он загружает эти представления до того, как декоратор можно будет исправить. Поэтому, если я жестко кодирую foo_uri вместо обратного, я могу использовать @patch('app.views.status_check', lambda func: func), и это работает. - person John P. Neumann; 05.04.2013