django.test.testcases.TestCase が良さげなので、 doctest 用にカスタマイズ。 status_code と template のテストは網羅的にやったりするけど、端から見るとよくわかんないチェックコードになってしまったり。 整理しようと思ってたのでやってみた。 継承して raise のとこだけ差し替えようとしたけどうまくいかず。 どうやればいいんだろうか。 まるごとコピペして raise を print に、 assertUrlsDict を追加して、 assertRedirects は相対パスを扱えるようにしてみました。 Client の設定と fixture を扱えるようにしなきゃ。 あと、メッセージが長いのがイヤン。 最新版は この辺 にUPされる予定です。
utils/tests.py
>>> from doctests import Test
>>> from django.test.client import Client
>>> t = Test()
>>> isinstance(t.client, Client)
True
>>> t.client == t.c
True
#"URL": (status_code, "template or redirect_url"),
>>> urls = {\
"/django/doc-ja/index-non_0.1": (301, "/django/doc-ja/index/"),\
"/django/doc-ja/index-non_0.2": (301, "/"),\
"/django/doc-ja/index-non_0.3/": (404, ("404.html", "base.html", )),\
"/django/doc-ja/index-non_0.4/": (404, "non-template"),\
\
"/": (200, ),\
"/blog/": (200, ""),\
"/django/doc-ja/index/": (200, "doc/rest.html"),\
"/django/doc-ja/tasting/": (200, "/django/doc-ja/index/"),\
"/django/doc-ja/settings/": (2000, ""),\
"/django/doc-ja/webdesign/": ("200", ""),\
}
>>> t.assertUrlsDict(urls)
Response didn't redirect as expected: Reponse code was 404 (expected 200). in '/blog/'
Response didn't redirect as expected: Reponse code was 404 (expected 200). in '/django/doc-ja/tasting/'
Template '/django/doc-ja/index/' was not one of the templates used to render the response. Templates used: ['404.html', 'base.html', 'google-analytics.html']
Response didn't redirect as expected: Reponse code was 200 (expected 2000). in '/django/doc-ja/settings/'
Template 'non-template' was not one of the templates used to render the response. Templates used: ['404.html', 'base.html', 'google-analytics.html']
Response didn't redirect as expected: Reponse code was 404 (expected 200). in '/'
Response redirected to '/django/doc-ja/index/', expected '/'
Bad test. '/django/doc-ja/webdesign/': ('200', '')
utils/doctests.py
from urlparse import urlparse, urljoin
from django.test.client import Client
class Test(object):
def __init__(self, **extra):
_extra = {}
if hasattr(self, 'cookies'):
_extra["HTTP_COOKIE"] = self.cookies
if hasattr(self, 'ipaddr'):
_extra["REMOTE_ADDR"] = self.ipaddr
_extra.update(extra)
self.client = Client(**_extra)
self.c = self.client
redirect_status_code = (301, 302)
def assertUrlsDict(self, urls_dict):
for key, value in urls_dict.items():
if value[0] in self.redirect_status_code:
base_path, status_code, expected_path = key, value[0], value[1]
response = self.client.get(base_path)
self.assertRedirects(response, expected_path, status_code=status_code, \
base_path=base_path)
elif isinstance(value[0], int):
base_path, status_code = key, value[0]
response = self.client.get(base_path)
self.assertEqual(response.status_code, status_code,
"Response didn't redirect as expected: Reponse code was %d (expected %d). in '%s'" %
(response.status_code, status_code, key))
try:
if not value[1]:
continue
except IndexError:
continue
if isinstance(value[1], str):
self.assertTemplateUsed(response, value[1])
else:
[self.assertTemplateUsed(response, template_name) for template_name in value[1]]
else:
print "Bad test. '%s': %s" % (key, value)
def assertRedirects(self, response, expected_path, status_code=302, target_status_code=200, \
base_path=None):
"""Assert that a response redirected to a specific URL, and that the
redirect URL can be loaded.
"""
self.assertEqual(response.status_code, status_code,
"Response didn't redirect as expected: Reponse code was %d (expected %d)" %
(response.status_code, status_code))
scheme, netloc, path, params, query, fragment = urlparse(response['Location'])
self.assertEqual(path, expected_path,
"Response redirected to '%s', expected '%s'" % (path, expected_path))
path = urljoin(base_path or "", path)
redirect_response = self.client.get(path)
self.assertEqual(redirect_response.status_code, target_status_code,
"Couldn't retrieve redirection page '%s': response code was %d (expected %d)" %
(path, redirect_response.status_code, target_status_code))
def assertContains(self, response, text, count=1, status_code=200):
"""Assert that a response indicates that a page was retreived successfully,
(i.e., the HTTP status code was as expected), and that ``text`` occurs ``count``
times in the content of the response.
"""
self.assertEqual(response.status_code, status_code,
"Couldn't retrieve page: Response code was %d (expected %d)'" %
(response.status_code, status_code))
real_count = response.content.count(text)
self.assertEqual(real_count, count,
"Found %d instances of '%s' in response (expected %d)" % (real_count, text, count))
def assertFormError(self, response, form, field, errors):
"Assert that a form used to render the response has a specific field error"
if not response.context:
self.fail('Response did not use any contexts to render the response')
# If there is a single context, put it into a list to simplify processing
if not isinstance(response.context, list):
contexts = [response.context]
else:
contexts = response.context
# If a single error string is provided, make it a list to simplify processing
if not isinstance(errors, list):
errors = [errors]
# Search all contexts for the error.
found_form = False
for i,context in enumerate(contexts):
if form in context:
found_form = True
for err in errors:
if field:
if field in context[form].errors:
self.failUnless(err in context[form].errors[field],
"The field '%s' on form '%s' in context %d does not contain the error '%s' (actual errors: %s)" %
(field, form, i, err, list(context[form].errors[field])))
elif field in context[form].fields:
self.fail("The field '%s' on form '%s' in context %d contains no errors" %
(field, form, i))
else:
self.fail("The form '%s' in context %d does not contain the field '%s'" % (form, i, field))
else:
self.failUnless(err in context[form].non_field_errors(),
"The form '%s' in context %d does not contain the non-field error '%s' (actual errors: %s)" %
(form, i, err, list(context[form].non_field_errors())))
if not found_form:
self.fail("The form '%s' was not used to render the response" % form)
def assertTemplateUsed(self, response, template_name):
"Assert that the template with the provided name was used in rendering the response"
if isinstance(response.template, list):
template_names = [t.name for t in response.template]
self.failUnless(template_name in template_names,
"Template '%s' was not one of the templates used to render the response. Templates used: %s" %
(template_name, template_names))
elif response.template:
self.assertEqual(template_name, response.template.name,
"Template '%s' was not used to render the response. Actual template was '%s'" %
(template_name, response.template.name))
else:
self.fail('No templates used to render the response')
def assertTemplateNotUsed(self, response, template_name):
"Assert that the template with the provided name was NOT used in rendering the response"
if isinstance(response.template, list):
self.failIf(template_name in [t.name for t in response.template],
"Template '%s' was used unexpectedly in rendering the response" % template_name)
elif response.template:
self.assertNotEqual(template_name, response.template.name,
"Template '%s' was used unexpectedly in rendering the response" % template_name)
failureException = AssertionError
def fail(self, msg=None):
"""Fail immediately, with the given message."""
print msg
def failIf(self, expr, msg=None):
"Fail the test if the expression is true."
if expr: print msg
def failUnless(self, expr, msg=None):
"""Fail the test unless the expression is true."""
if not expr: print msg
def failUnlessRaises(self, excClass, callableObj, *args, **kwargs):
"""Fail unless an exception of class excClass is thrown
by callableObj when invoked with arguments args and keyword
arguments kwargs. If a different type of exception is
thrown, it will not be caught, and the test case will be
deemed to have suffered an error, exactly as for an
unexpected exception.
"""
try:
callableObj(*args, **kwargs)
except excClass:
return
else:
if hasattr(excClass,'__name__'): excName = excClass.__name__
else: excName = str(excClass)
print "%s not raised" % excName
def failUnlessEqual(self, first, second, msg=None):
"""Fail if the two objects are unequal as determined by the '=='
operator.
"""
if not first == second:
print (msg or '%r != %r' % (first, second))
def failIfEqual(self, first, second, msg=None):
"""Fail if the two objects are equal as determined by the '=='
operator.
"""
if first == second:
print (msg or '%r == %r' % (first, second))
def failUnlessAlmostEqual(self, first, second, places=7, msg=None):
"""Fail if the two objects are unequal as determined by their
difference rounded to the given number of decimal places
(default 7) and comparing to zero.
Note that decimal places (from zero) are usually not the same
as significant digits (measured from the most signficant digit).
"""
if round(second-first, places) != 0:
print (msg or '%r != %r within %r places' % (first, second, places))
def failIfAlmostEqual(self, first, second, places=7, msg=None):
"""Fail if the two objects are equal as determined by their
difference rounded to the given number of decimal places
(default 7) and comparing to zero.
Note that decimal places (from zero) are usually not the same
as significant digits (measured from the most signficant digit).
"""
if round(second-first, places) == 0:
print (msg or '%r == %r within %r places' % (first, second, places))
assertEqual = assertEquals = failUnlessEqual
assertNotEqual = assertNotEquals = failIfEqual
assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual
assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual
assertRaises = failUnlessRaises
assert_ = assertTrue = failUnless
assertFalse = failIf
