Django 测试:查看创建错误响应的回溯
Django Testing: See traceback where wrong Response gets created
此模式来自 django 文档:
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 网络客户端的子类):Django: assertEqual(response.status_code, 200): I want to see useful stack of functions calls
How do I see the traceback if the assertion fails without debugging
如果断言失败,则没有回溯。 client.get()
并没有失败,它只是返回了与您预期不同的响应。
您可以使用 pdb 单步执行 client.get()
调用,看看它返回意外响应的原因。
我认为可以通过创建一个 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)
也许这对你有用:
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
模块记录它,或者将其添加为 [=16] 的消息=] 方法。没有调试器,一旦你assert
,打印任何有用的东西就太晚了(通常)。
您还可以配置 logging
并添加处理程序以将消息保存在内存中,并打印所有这些;在自定义断言方法或自定义测试运行程序中。
我受到@Fush 提出的解决方案的启发,但我的代码使用的是 assertRedirects,这是一个更长的方法,而且代码太多,无法复制而不会对自己感到难过。
我花了一些时间弄清楚如何为每个断言调用 super() 并想出了这个。我已经包含了 2 个示例断言方法 - 它们基本上都是相同的。也许一些聪明的灵魂可以想到一些元类魔法,它对所有以 'response' 作为第一个参数的方法都这样做。
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)
我将 django 网络客户端子类化,得到这个:
用法
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 响应,则会导致长时间异常。如果您不害怕长异常,您会很快看到问题的根源。这就是我想要的,我很高兴。
学分
这是基于这个问题的其他答案。
此模式来自 django 文档:
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 网络客户端的子类):Django: assertEqual(response.status_code, 200): I want to see useful stack of functions calls
How do I see the traceback if the assertion fails without debugging
如果断言失败,则没有回溯。 client.get()
并没有失败,它只是返回了与您预期不同的响应。
您可以使用 pdb 单步执行 client.get()
调用,看看它返回意外响应的原因。
我认为可以通过创建一个 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)
也许这对你有用:
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
模块记录它,或者将其添加为 [=16] 的消息=] 方法。没有调试器,一旦你assert
,打印任何有用的东西就太晚了(通常)。
您还可以配置 logging
并添加处理程序以将消息保存在内存中,并打印所有这些;在自定义断言方法或自定义测试运行程序中。
我受到@Fush 提出的解决方案的启发,但我的代码使用的是 assertRedirects,这是一个更长的方法,而且代码太多,无法复制而不会对自己感到难过。
我花了一些时间弄清楚如何为每个断言调用 super() 并想出了这个。我已经包含了 2 个示例断言方法 - 它们基本上都是相同的。也许一些聪明的灵魂可以想到一些元类魔法,它对所有以 'response' 作为第一个参数的方法都这样做。
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)
我将 django 网络客户端子类化,得到这个:
用法
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 响应,则会导致长时间异常。如果您不害怕长异常,您会很快看到问题的根源。这就是我想要的,我很高兴。
学分
这是基于这个问题的其他答案。