Django测试:查看创建错误响应的回溯

时间:2015-09-28 09:53:38

标签: python django unit-testing http-response-codes

此模式来自django docs:

class SimpleTest(unittest.TestCase):
    def test_details(self):
        client = Client()
        response = client.get('/customer/details/')
        self.assertEqual(response.status_code, 200)

来自:https://docs.djangoproject.com/en/1.8/topics/testing/tools/#default-test-client

如果测试失败,则错误消息没有多大帮助。例如,如果status_code是302,那么我会看到302 != 200

现在的问题是:创建了错误的HTTPResponse在哪里?

我希望看到解释器的堆栈跟踪,其中创建了错误的HTTPResponse对象。

我阅读了assertions of django的文档,但没有找到匹配的方法。

更新

这是一个普遍的问题:如果断言失败,如何立即查看所需信息?由于这些断言(self.assertEqual(response.status_code, 200))很常见,我不想开始调试。

2016年更新

我又有了同样的想法,发现目前的答案不是100%容易。我写了一个新的答案,它有一个简单易用的解决方案(django web客户端的子类):Django: assertEqual(response.status_code, 200): I want to see useful stack of functions calls

5 个答案:

答案 0 :(得分:2)

  

如果断言失败而没有调试

,如何查看回溯

如果断言失败,则没有回溯。 client.get()没有失败,只是返回了与您预期不同的响应。

您可以使用pdb逐步完成client.get()调用,并查看其返回意外回复的原因。

答案 1 :(得分:2)

我认为可以通过创建一个TestCase子类来实现,monkeypatches django.http.response.HttpResponseBase.__init__()用于记录堆栈跟踪并将其存储在Response对象上,然后编写assertResponseCodeEquals(response, status_code=200)在失败时打印存储的堆栈跟踪的方法,以显示Response的创建位置。

我自己实际上可以真正使用解决方案,并且可能会考虑实现它。

更新: 这是一个v1实现,可以使用一些细化(例如,只打印堆栈跟踪的相关行)。

import mock
from traceback import extract_stack, format_list
from django.test.testcases import TestCase
from django.http.response import HttpResponseBase

orig_response_init = HttpResponseBase.__init__

def new_response_init(self, *args, **kwargs):
    orig_response_init(self, *args, **kwargs)
    self._init_stack = extract_stack()

class ResponseTracebackTestCase(TestCase):
    @classmethod
    def setUpClass(cls):
        cls.patcher = mock.patch.object(HttpResponseBase, '__init__', new_response_init)
        cls.patcher.start()

    @classmethod
    def tearDownClass(cls):
        cls.patcher.stop()

    def assertResponseCodeEquals(self, response, status_code=200):
        self.assertEqual(response.status_code, status_code,
            "Response code was '%s', expected '%s'" % (
                response.status_code, status_code,
            ) + '\n' + ''.join(format_list(response._init_stack))
        )

class MyTestCase(ResponseTracebackTestCase):
    def test_index_page_returns_200(self):
        response = self.client.get('/')
        self.assertResponseCodeEquals(response, 200)

答案 2 :(得分:1)

我受到了@Fush提出的解决方案的启发,但是我的代码使用的是assertRedirects,这是一个更长的方法,并且有点太多的代码可以复制而不会对自己感觉不好。

我花了一些时间搞清楚如何为每个断言调用super()并想出这个。我已经包含了2个示例断言方法 - 它们基本上都是相同的。也许一些聪明的灵魂可以想到一些元类魔法,它会对所有采取“反应”的方法做到这一点。作为他们的第一个论点。

from bs4 import BeautifulSoup
from django.test.testcases import TestCase


class ResponseTracebackTestCase(TestCase):

    def _display_response_traceback(self, e, content):
        soup = BeautifulSoup(content)
        assert False, u'\n\nOriginal Traceback:\n\n{}'.format(
            soup.find("textarea", {"id": "traceback_area"}).text
        )

    def assertRedirects(self, response, *args, **kwargs):
        try:
            super(ResponseTracebackTestCase, self).assertRedirects(response, *args, **kwargs)
        except Exception as e:
            self._display_response_traceback(e, response.content)

    def assertContains(self, response, *args, **kwargs):
        try:
            super(ResponseTracebackTestCase, self).assertContains(response, *args, **kwargs)
        except Exception as e:
            self._display_response_traceback(e, response.content)

答案 3 :(得分:0)

也许这对你有用:

class SimpleTest(unittest.TestCase):
    @override_settings(DEBUG=True)
    def test_details(self):
        client = Client()
        response = client.get('/customer/details/')
        self.assertEqual(response.status_code, 200, response.content)

使用@override_settings拥有DEBUG=True将拥有堆栈跟踪,就像您在DEBUG模式下运行实例一样。

其次,为了提供回复的内容,您需要print或使用logging模块进行记录,或将其添加为assert的消息方法。没有调试器,一旦assert,打印任何有用的东西(通常)都为时已晚。

您还可以配置logging并添加处理程序以将消息保存在内存中,并打印所有内容;无论是在自定义断言方法还是在自定义测试运行器中。

答案 4 :(得分:-1)

我将django Web客户端子类化,得到这个:

用法

def test_foo(self):
    ...
    MyClient().get(url, assert_status=200)

实施

from django.test import Client

class MyClient(Client):
    def generic(self, method, path, data='',
                content_type='application/octet-stream', secure=False,
                assert_status=None,
                **extra):
        if assert_status:
            return self.assert_status(assert_status, super(MyClient, self).generic, method, path, data, content_type, secure, **extra)
        return super(MyClient, self).generic(method, path, data, content_type, secure, **extra)

    @classmethod
    def assert_status(cls, status_code, method_pointer, *args, **kwargs):
        assert hasattr(method_pointer, '__call__'), 'Method pointer needed, looks like the result of a method call: %r' % (method_pointer)

        def new_init(self, *args, **kwargs):
            orig_response_init(self, *args, **kwargs)
            if not status_code == self.status_code:
                raise HTTPResponseStatusCodeAssertionError('should=%s is=%s' % (status_code, self.status_code))
        def reraise_exception(*args, **kwargs):
            raise

        with mock.patch('django.core.handlers.base.BaseHandler.handle_uncaught_exception', reraise_exception):
            with mock.patch.object(HttpResponseBase, '__init__', new_init):
                return method_pointer(*args, **kwargs)

结论

如果创建了具有错误状态代码的http响应,则会导致长异常。如果你不害怕长期例外,你会非常快速地看到问题的根源。这就是我想要的,我很高兴。

积分

这是基于这个问题的其他答案。