Fixed #20004 -- Moved non DB-related assertions to SimpleTestCase.

Thanks zalew for the suggestion and work on a patch.

Also updated, tweaked and fixed testing documentation.
This commit is contained in:
Ramiro Morales 2013-05-18 19:04:34 -03:00
parent 69523c1ba3
commit 0a50311063
9 changed files with 425 additions and 370 deletions

View File

@ -231,6 +231,10 @@ class _AssertTemplateNotUsedContext(_AssertTemplateUsedContext):
class SimpleTestCase(ut2.TestCase): class SimpleTestCase(ut2.TestCase):
# The class we'll use for the test client self.client.
# Can be overridden in derived classes.
client_class = Client
_warn_txt = ("save_warnings_state/restore_warnings_state " _warn_txt = ("save_warnings_state/restore_warnings_state "
"django.test.*TestCase methods are deprecated. Use Python's " "django.test.*TestCase methods are deprecated. Use Python's "
"warnings.catch_warnings context manager instead.") "warnings.catch_warnings context manager instead.")
@ -264,10 +268,31 @@ class SimpleTestCase(ut2.TestCase):
return return
def _pre_setup(self): def _pre_setup(self):
pass """Performs any pre-test setup. This includes:
* If the Test Case class has a 'urls' member, replace the
ROOT_URLCONF with it.
* Clearing the mail test outbox.
"""
self.client = self.client_class()
self._urlconf_setup()
mail.outbox = []
def _urlconf_setup(self):
set_urlconf(None)
if hasattr(self, 'urls'):
self._old_root_urlconf = settings.ROOT_URLCONF
settings.ROOT_URLCONF = self.urls
clear_url_caches()
def _post_teardown(self): def _post_teardown(self):
pass self._urlconf_teardown()
def _urlconf_teardown(self):
set_urlconf(None)
if hasattr(self, '_old_root_urlconf'):
settings.ROOT_URLCONF = self._old_root_urlconf
clear_url_caches()
def save_warnings_state(self): def save_warnings_state(self):
""" """
@ -291,258 +316,6 @@ class SimpleTestCase(ut2.TestCase):
""" """
return override_settings(**kwargs) return override_settings(**kwargs)
def assertRaisesMessage(self, expected_exception, expected_message,
callable_obj=None, *args, **kwargs):
"""
Asserts that the message in a raised exception matches the passed
value.
Args:
expected_exception: Exception class expected to be raised.
expected_message: expected error message string value.
callable_obj: Function to be called.
args: Extra args.
kwargs: Extra kwargs.
"""
return six.assertRaisesRegex(self, expected_exception,
re.escape(expected_message), callable_obj, *args, **kwargs)
def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
field_kwargs=None, empty_value=''):
"""
Asserts that a form field behaves correctly with various inputs.
Args:
fieldclass: the class of the field to be tested.
valid: a dictionary mapping valid inputs to their expected
cleaned values.
invalid: a dictionary mapping invalid inputs to one or more
raised error messages.
field_args: the args passed to instantiate the field
field_kwargs: the kwargs passed to instantiate the field
empty_value: the expected clean output for inputs in empty_values
"""
if field_args is None:
field_args = []
if field_kwargs is None:
field_kwargs = {}
required = fieldclass(*field_args, **field_kwargs)
optional = fieldclass(*field_args,
**dict(field_kwargs, required=False))
# test valid inputs
for input, output in valid.items():
self.assertEqual(required.clean(input), output)
self.assertEqual(optional.clean(input), output)
# test invalid inputs
for input, errors in invalid.items():
with self.assertRaises(ValidationError) as context_manager:
required.clean(input)
self.assertEqual(context_manager.exception.messages, errors)
with self.assertRaises(ValidationError) as context_manager:
optional.clean(input)
self.assertEqual(context_manager.exception.messages, errors)
# test required inputs
error_required = [force_text(required.error_messages['required'])]
for e in required.empty_values:
with self.assertRaises(ValidationError) as context_manager:
required.clean(e)
self.assertEqual(context_manager.exception.messages,
error_required)
self.assertEqual(optional.clean(e), empty_value)
# test that max_length and min_length are always accepted
if issubclass(fieldclass, CharField):
field_kwargs.update({'min_length':2, 'max_length':20})
self.assertTrue(isinstance(fieldclass(*field_args, **field_kwargs),
fieldclass))
def assertHTMLEqual(self, html1, html2, msg=None):
"""
Asserts that two HTML snippets are semantically the same.
Whitespace in most cases is ignored, and attribute ordering is not
significant. The passed-in arguments must be valid HTML.
"""
dom1 = assert_and_parse_html(self, html1, msg,
'First argument is not valid HTML:')
dom2 = assert_and_parse_html(self, html2, msg,
'Second argument is not valid HTML:')
if dom1 != dom2:
standardMsg = '%s != %s' % (
safe_repr(dom1, True), safe_repr(dom2, True))
diff = ('\n' + '\n'.join(difflib.ndiff(
six.text_type(dom1).splitlines(),
six.text_type(dom2).splitlines())))
standardMsg = self._truncateMessage(standardMsg, diff)
self.fail(self._formatMessage(msg, standardMsg))
def assertHTMLNotEqual(self, html1, html2, msg=None):
"""Asserts that two HTML snippets are not semantically equivalent."""
dom1 = assert_and_parse_html(self, html1, msg,
'First argument is not valid HTML:')
dom2 = assert_and_parse_html(self, html2, msg,
'Second argument is not valid HTML:')
if dom1 == dom2:
standardMsg = '%s == %s' % (
safe_repr(dom1, True), safe_repr(dom2, True))
self.fail(self._formatMessage(msg, standardMsg))
def assertInHTML(self, needle, haystack, count = None, msg_prefix=''):
needle = assert_and_parse_html(self, needle, None,
'First argument is not valid HTML:')
haystack = assert_and_parse_html(self, haystack, None,
'Second argument is not valid HTML:')
real_count = haystack.count(needle)
if count is not None:
self.assertEqual(real_count, count,
msg_prefix + "Found %d instances of '%s' in response"
" (expected %d)" % (real_count, needle, count))
else:
self.assertTrue(real_count != 0,
msg_prefix + "Couldn't find '%s' in response" % needle)
def assertJSONEqual(self, raw, expected_data, msg=None):
try:
data = json.loads(raw)
except ValueError:
self.fail("First argument is not valid JSON: %r" % raw)
if isinstance(expected_data, six.string_types):
try:
expected_data = json.loads(expected_data)
except ValueError:
self.fail("Second argument is not valid JSON: %r" % expected_data)
self.assertEqual(data, expected_data, msg=msg)
def assertXMLEqual(self, xml1, xml2, msg=None):
"""
Asserts that two XML snippets are semantically the same.
Whitespace in most cases is ignored, and attribute ordering is not
significant. The passed-in arguments must be valid XML.
"""
try:
result = compare_xml(xml1, xml2)
except Exception as e:
standardMsg = 'First or second argument is not valid XML\n%s' % e
self.fail(self._formatMessage(msg, standardMsg))
else:
if not result:
standardMsg = '%s != %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
self.fail(self._formatMessage(msg, standardMsg))
def assertXMLNotEqual(self, xml1, xml2, msg=None):
"""
Asserts that two XML snippets are not semantically equivalent.
Whitespace in most cases is ignored, and attribute ordering is not
significant. The passed-in arguments must be valid XML.
"""
try:
result = compare_xml(xml1, xml2)
except Exception as e:
standardMsg = 'First or second argument is not valid XML\n%s' % e
self.fail(self._formatMessage(msg, standardMsg))
else:
if result:
standardMsg = '%s == %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
self.fail(self._formatMessage(msg, standardMsg))
class TransactionTestCase(SimpleTestCase):
# The class we'll use for the test client self.client.
# Can be overridden in derived classes.
client_class = Client
# Subclasses can ask for resetting of auto increment sequence before each
# test case
reset_sequences = False
def _pre_setup(self):
"""Performs any pre-test setup. This includes:
* Flushing the database.
* If the Test Case class has a 'fixtures' member, installing the
named fixtures.
* If the Test Case class has a 'urls' member, replace the
ROOT_URLCONF with it.
* Clearing the mail test outbox.
"""
self.client = self.client_class()
self._fixture_setup()
self._urlconf_setup()
mail.outbox = []
def _databases_names(self, include_mirrors=True):
# If the test case has a multi_db=True flag, act on all databases,
# including mirrors or not. Otherwise, just on the default DB.
if getattr(self, 'multi_db', False):
return [alias for alias in connections
if include_mirrors or not connections[alias].settings_dict['TEST_MIRROR']]
else:
return [DEFAULT_DB_ALIAS]
def _reset_sequences(self, db_name):
conn = connections[db_name]
if conn.features.supports_sequence_reset:
sql_list = \
conn.ops.sequence_reset_by_name_sql(no_style(),
conn.introspection.sequence_list())
if sql_list:
with transaction.commit_on_success_unless_managed(using=db_name):
cursor = conn.cursor()
for sql in sql_list:
cursor.execute(sql)
def _fixture_setup(self):
for db_name in self._databases_names(include_mirrors=False):
# Reset sequences
if self.reset_sequences:
self._reset_sequences(db_name)
if hasattr(self, 'fixtures'):
# We have to use this slightly awkward syntax due to the fact
# that we're using *args and **kwargs together.
call_command('loaddata', *self.fixtures,
**{'verbosity': 0, 'database': db_name, 'skip_validation': True})
def _urlconf_setup(self):
set_urlconf(None)
if hasattr(self, 'urls'):
self._old_root_urlconf = settings.ROOT_URLCONF
settings.ROOT_URLCONF = self.urls
clear_url_caches()
def _post_teardown(self):
""" Performs any post-test things. This includes:
* Putting back the original ROOT_URLCONF if it was changed.
* Force closing the connection, so that the next test gets
a clean cursor.
"""
self._fixture_teardown()
self._urlconf_teardown()
# Some DB cursors include SQL statements as part of cursor
# creation. If you have a test that does rollback, the effect
# of these statements is lost, which can effect the operation
# of tests (e.g., losing a timezone setting causing objects to
# be created with the wrong time).
# To make sure this doesn't happen, get a clean connection at the
# start of every test.
for conn in connections.all():
conn.close()
def _fixture_teardown(self):
for db in self._databases_names(include_mirrors=False):
call_command('flush', verbosity=0, interactive=False, database=db,
skip_validation=True, reset_sequences=False)
def _urlconf_teardown(self):
set_urlconf(None)
if hasattr(self, '_old_root_urlconf'):
settings.ROOT_URLCONF = self._old_root_urlconf
clear_url_caches()
def assertRedirects(self, response, expected_url, status_code=302, def assertRedirects(self, response, expected_url, status_code=302,
target_status_code=200, host=None, msg_prefix=''): target_status_code=200, host=None, msg_prefix=''):
"""Asserts that a response redirected to a specific URL, and that the """Asserts that a response redirected to a specific URL, and that the
@ -787,6 +560,236 @@ class TransactionTestCase(SimpleTestCase):
msg_prefix + "Template '%s' was used unexpectedly in rendering" msg_prefix + "Template '%s' was used unexpectedly in rendering"
" the response" % template_name) " the response" % template_name)
def assertRaisesMessage(self, expected_exception, expected_message,
callable_obj=None, *args, **kwargs):
"""
Asserts that the message in a raised exception matches the passed
value.
Args:
expected_exception: Exception class expected to be raised.
expected_message: expected error message string value.
callable_obj: Function to be called.
args: Extra args.
kwargs: Extra kwargs.
"""
return six.assertRaisesRegex(self, expected_exception,
re.escape(expected_message), callable_obj, *args, **kwargs)
def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
field_kwargs=None, empty_value=''):
"""
Asserts that a form field behaves correctly with various inputs.
Args:
fieldclass: the class of the field to be tested.
valid: a dictionary mapping valid inputs to their expected
cleaned values.
invalid: a dictionary mapping invalid inputs to one or more
raised error messages.
field_args: the args passed to instantiate the field
field_kwargs: the kwargs passed to instantiate the field
empty_value: the expected clean output for inputs in empty_values
"""
if field_args is None:
field_args = []
if field_kwargs is None:
field_kwargs = {}
required = fieldclass(*field_args, **field_kwargs)
optional = fieldclass(*field_args,
**dict(field_kwargs, required=False))
# test valid inputs
for input, output in valid.items():
self.assertEqual(required.clean(input), output)
self.assertEqual(optional.clean(input), output)
# test invalid inputs
for input, errors in invalid.items():
with self.assertRaises(ValidationError) as context_manager:
required.clean(input)
self.assertEqual(context_manager.exception.messages, errors)
with self.assertRaises(ValidationError) as context_manager:
optional.clean(input)
self.assertEqual(context_manager.exception.messages, errors)
# test required inputs
error_required = [force_text(required.error_messages['required'])]
for e in required.empty_values:
with self.assertRaises(ValidationError) as context_manager:
required.clean(e)
self.assertEqual(context_manager.exception.messages,
error_required)
self.assertEqual(optional.clean(e), empty_value)
# test that max_length and min_length are always accepted
if issubclass(fieldclass, CharField):
field_kwargs.update({'min_length':2, 'max_length':20})
self.assertTrue(isinstance(fieldclass(*field_args, **field_kwargs),
fieldclass))
def assertHTMLEqual(self, html1, html2, msg=None):
"""
Asserts that two HTML snippets are semantically the same.
Whitespace in most cases is ignored, and attribute ordering is not
significant. The passed-in arguments must be valid HTML.
"""
dom1 = assert_and_parse_html(self, html1, msg,
'First argument is not valid HTML:')
dom2 = assert_and_parse_html(self, html2, msg,
'Second argument is not valid HTML:')
if dom1 != dom2:
standardMsg = '%s != %s' % (
safe_repr(dom1, True), safe_repr(dom2, True))
diff = ('\n' + '\n'.join(difflib.ndiff(
six.text_type(dom1).splitlines(),
six.text_type(dom2).splitlines())))
standardMsg = self._truncateMessage(standardMsg, diff)
self.fail(self._formatMessage(msg, standardMsg))
def assertHTMLNotEqual(self, html1, html2, msg=None):
"""Asserts that two HTML snippets are not semantically equivalent."""
dom1 = assert_and_parse_html(self, html1, msg,
'First argument is not valid HTML:')
dom2 = assert_and_parse_html(self, html2, msg,
'Second argument is not valid HTML:')
if dom1 == dom2:
standardMsg = '%s == %s' % (
safe_repr(dom1, True), safe_repr(dom2, True))
self.fail(self._formatMessage(msg, standardMsg))
def assertInHTML(self, needle, haystack, count=None, msg_prefix=''):
needle = assert_and_parse_html(self, needle, None,
'First argument is not valid HTML:')
haystack = assert_and_parse_html(self, haystack, None,
'Second argument is not valid HTML:')
real_count = haystack.count(needle)
if count is not None:
self.assertEqual(real_count, count,
msg_prefix + "Found %d instances of '%s' in response"
" (expected %d)" % (real_count, needle, count))
else:
self.assertTrue(real_count != 0,
msg_prefix + "Couldn't find '%s' in response" % needle)
def assertJSONEqual(self, raw, expected_data, msg=None):
try:
data = json.loads(raw)
except ValueError:
self.fail("First argument is not valid JSON: %r" % raw)
if isinstance(expected_data, six.string_types):
try:
expected_data = json.loads(expected_data)
except ValueError:
self.fail("Second argument is not valid JSON: %r" % expected_data)
self.assertEqual(data, expected_data, msg=msg)
def assertXMLEqual(self, xml1, xml2, msg=None):
"""
Asserts that two XML snippets are semantically the same.
Whitespace in most cases is ignored, and attribute ordering is not
significant. The passed-in arguments must be valid XML.
"""
try:
result = compare_xml(xml1, xml2)
except Exception as e:
standardMsg = 'First or second argument is not valid XML\n%s' % e
self.fail(self._formatMessage(msg, standardMsg))
else:
if not result:
standardMsg = '%s != %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
self.fail(self._formatMessage(msg, standardMsg))
def assertXMLNotEqual(self, xml1, xml2, msg=None):
"""
Asserts that two XML snippets are not semantically equivalent.
Whitespace in most cases is ignored, and attribute ordering is not
significant. The passed-in arguments must be valid XML.
"""
try:
result = compare_xml(xml1, xml2)
except Exception as e:
standardMsg = 'First or second argument is not valid XML\n%s' % e
self.fail(self._formatMessage(msg, standardMsg))
else:
if result:
standardMsg = '%s == %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
self.fail(self._formatMessage(msg, standardMsg))
class TransactionTestCase(SimpleTestCase):
# Subclasses can ask for resetting of auto increment sequence before each
# test case
reset_sequences = False
def _pre_setup(self):
"""Performs any pre-test setup. This includes:
* Flushing the database.
* If the Test Case class has a 'fixtures' member, installing the
named fixtures.
"""
super(TransactionTestCase, self)._pre_setup()
self._fixture_setup()
def _databases_names(self, include_mirrors=True):
# If the test case has a multi_db=True flag, act on all databases,
# including mirrors or not. Otherwise, just on the default DB.
if getattr(self, 'multi_db', False):
return [alias for alias in connections
if include_mirrors or not connections[alias].settings_dict['TEST_MIRROR']]
else:
return [DEFAULT_DB_ALIAS]
def _reset_sequences(self, db_name):
conn = connections[db_name]
if conn.features.supports_sequence_reset:
sql_list = \
conn.ops.sequence_reset_by_name_sql(no_style(),
conn.introspection.sequence_list())
if sql_list:
with transaction.commit_on_success_unless_managed(using=db_name):
cursor = conn.cursor()
for sql in sql_list:
cursor.execute(sql)
def _fixture_setup(self):
for db_name in self._databases_names(include_mirrors=False):
# Reset sequences
if self.reset_sequences:
self._reset_sequences(db_name)
if hasattr(self, 'fixtures'):
# We have to use this slightly awkward syntax due to the fact
# that we're using *args and **kwargs together.
call_command('loaddata', *self.fixtures,
**{'verbosity': 0, 'database': db_name, 'skip_validation': True})
def _post_teardown(self):
"""Performs any post-test things. This includes:
* Putting back the original ROOT_URLCONF if it was changed.
* Force closing the connection, so that the next test gets
a clean cursor.
"""
self._fixture_teardown()
super(TransactionTestCase, self)._post_teardown()
# Some DB cursors include SQL statements as part of cursor
# creation. If you have a test that does rollback, the effect
# of these statements is lost, which can effect the operation
# of tests (e.g., losing a timezone setting causing objects to
# be created with the wrong time).
# To make sure this doesn't happen, get a clean connection at the
# start of every test.
for conn in connections.all():
conn.close()
def _fixture_teardown(self):
for db_name in self._databases_names(include_mirrors=False):
call_command('flush', verbosity=0, interactive=False, database=db_name,
skip_validation=True, reset_sequences=False)
def assertQuerysetEqual(self, qs, values, transform=repr, ordered=True): def assertQuerysetEqual(self, qs, values, transform=repr, ordered=True):
items = six.moves.map(transform, qs) items = six.moves.map(transform, qs)
if not ordered: if not ordered:
@ -841,14 +844,14 @@ class TestCase(TransactionTestCase):
# Remove this when the legacy transaction management goes away. # Remove this when the legacy transaction management goes away.
disable_transaction_methods() disable_transaction_methods()
for db in self._databases_names(include_mirrors=False): for db_name in self._databases_names(include_mirrors=False):
if hasattr(self, 'fixtures'): if hasattr(self, 'fixtures'):
try: try:
call_command('loaddata', *self.fixtures, call_command('loaddata', *self.fixtures,
**{ **{
'verbosity': 0, 'verbosity': 0,
'commit': False, 'commit': False,
'database': db, 'database': db_name,
'skip_validation': True, 'skip_validation': True,
}) })
except Exception: except Exception:

View File

@ -503,8 +503,8 @@ of the process of creating polls.
message: "No polls are available." and verifies the ``latest_poll_list`` is message: "No polls are available." and verifies the ``latest_poll_list`` is
empty. Note that the :class:`django.test.TestCase` class provides some empty. Note that the :class:`django.test.TestCase` class provides some
additional assertion methods. In these examples, we use additional assertion methods. In these examples, we use
:meth:`~django.test.TestCase.assertContains()` and :meth:`~django.test.SimpleTestCase.assertContains()` and
:meth:`~django.test.TestCase.assertQuerysetEqual()`. :meth:`~django.test.TransactionTestCase.assertQuerysetEqual()`.
In ``test_index_view_with_a_past_poll``, we create a poll and verify that it In ``test_index_view_with_a_past_poll``, we create a poll and verify that it
appears in the list. appears in the list.

View File

@ -329,7 +329,7 @@ model:
.. admonition:: Serializing references to ``ContentType`` objects .. admonition:: Serializing references to ``ContentType`` objects
If you're serializing data (for example, when generating If you're serializing data (for example, when generating
:class:`~django.test.TestCase.fixtures`) from a model that implements :class:`~django.test.TransactionTestCase.fixtures`) from a model that implements
generic relations, you should probably be using a natural key to uniquely generic relations, you should probably be using a natural key to uniquely
identify related :class:`~django.contrib.contenttypes.models.ContentType` identify related :class:`~django.contrib.contenttypes.models.ContentType`
objects. See :ref:`natural keys<topics-serialization-natural-keys>` and objects. See :ref:`natural keys<topics-serialization-natural-keys>` and

View File

@ -154,7 +154,7 @@ requests. These include:
requests in tests. requests in tests.
* A new test assertion -- * A new test assertion --
:meth:`~django.test.TestCase.assertNumQueries` -- making it :meth:`~django.test.TransactionTestCase.assertNumQueries` -- making it
easier to test the database activity associated with a view. easier to test the database activity associated with a view.

View File

@ -299,7 +299,7 @@ requests. These include:
in tests. in tests.
* A new test assertion -- * A new test assertion --
:meth:`~django.test.TestCase.assertNumQueries` -- making it :meth:`~django.test.TransactionTestCase.assertNumQueries` -- making it
easier to test the database activity associated with a view. easier to test the database activity associated with a view.
* Support for lookups spanning relations in admin's * Support for lookups spanning relations in admin's

View File

@ -541,8 +541,8 @@ compare HTML directly with the new
:meth:`~django.test.SimpleTestCase.assertHTMLEqual` and :meth:`~django.test.SimpleTestCase.assertHTMLEqual` and
:meth:`~django.test.SimpleTestCase.assertHTMLNotEqual` assertions, or use :meth:`~django.test.SimpleTestCase.assertHTMLNotEqual` assertions, or use
the ``html=True`` flag with the ``html=True`` flag with
:meth:`~django.test.TestCase.assertContains` and :meth:`~django.test.SimpleTestCase.assertContains` and
:meth:`~django.test.TestCase.assertNotContains` to test whether the :meth:`~django.test.SimpleTestCase.assertNotContains` to test whether the
client's response contains a given HTML fragment. See the :ref:`assertions client's response contains a given HTML fragment. See the :ref:`assertions
documentation <assertions>` for more. documentation <assertions>` for more.
@ -1093,8 +1093,8 @@ wild, because they would confuse browsers too.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It's now possible to check whether a template was used within a block of It's now possible to check whether a template was used within a block of
code with :meth:`~django.test.TestCase.assertTemplateUsed` and code with :meth:`~django.test.SimpleTestCase.assertTemplateUsed` and
:meth:`~django.test.TestCase.assertTemplateNotUsed`. And they :meth:`~django.test.SimpleTestCase.assertTemplateNotUsed`. And they
can be used as a context manager:: can be used as a context manager::
with self.assertTemplateUsed('index.html'): with self.assertTemplateUsed('index.html'):

View File

@ -271,9 +271,10 @@ The changes in transaction management may result in additional statements to
create, release or rollback savepoints. This is more likely to happen with create, release or rollback savepoints. This is more likely to happen with
SQLite, since it didn't support savepoints until this release. SQLite, since it didn't support savepoints until this release.
If tests using :meth:`~django.test.TestCase.assertNumQueries` fail because of If tests using :meth:`~django.test.TransactionTestCase.assertNumQueries` fail
a higher number of queries than expected, check that the extra queries are because of a higher number of queries than expected, check that the extra
related to savepoints, and adjust the expected number of queries accordingly. queries are related to savepoints, and adjust the expected number of queries
accordingly.
Autocommit option for PostgreSQL Autocommit option for PostgreSQL
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -201,8 +201,8 @@ According to :pep:`3333`:
Specifically, :attr:`HttpResponse.content <django.http.HttpResponse.content>` Specifically, :attr:`HttpResponse.content <django.http.HttpResponse.content>`
contains ``bytes``, which may become an issue if you compare it with a contains ``bytes``, which may become an issue if you compare it with a
``str`` in your tests. The preferred solution is to rely on ``str`` in your tests. The preferred solution is to rely on
:meth:`~django.test.TestCase.assertContains` and :meth:`~django.test.SimpleTestCase.assertContains` and
:meth:`~django.test.TestCase.assertNotContains`. These methods accept a :meth:`~django.test.SimpleTestCase.assertNotContains`. These methods accept a
response and a unicode string as arguments. response and a unicode string as arguments.
Coding guidelines Coding guidelines

View File

@ -21,17 +21,16 @@ module defines tests using a class-based approach.
.. admonition:: unittest2 .. admonition:: unittest2
Python 2.7 introduced some major changes to the unittest library, Python 2.7 introduced some major changes to the ``unittest`` library,
adding some extremely useful features. To ensure that every Django adding some extremely useful features. To ensure that every Django
project can benefit from these new features, Django ships with a project can benefit from these new features, Django ships with a
copy of unittest2_, a copy of the Python 2.7 unittest library, copy of unittest2_, a copy of Python 2.7's ``unittest``, backported for
backported for Python 2.6 compatibility. Python 2.6 compatibility.
To access this library, Django provides the To access this library, Django provides the
``django.utils.unittest`` module alias. If you are using Python ``django.utils.unittest`` module alias. If you are using Python
2.7, or you have installed unittest2 locally, Django will map the 2.7, or you have installed ``unittest2`` locally, Django will map the alias
alias to the installed version of the unittest library. Otherwise, to it. Otherwise, Django will use its own bundled version of ``unittest2``.
Django will use its own bundled version of unittest2.
To use this alias, simply use:: To use this alias, simply use::
@ -41,8 +40,8 @@ module defines tests using a class-based approach.
import unittest import unittest
If you want to continue to use the base unittest library, you can -- If you want to continue to use the legacy ``unittest`` library, you can --
you just won't get any of the nice new unittest2 features. you just won't get any of the nice new ``unittest2`` features.
.. _unittest2: http://pypi.python.org/pypi/unittest2 .. _unittest2: http://pypi.python.org/pypi/unittest2
@ -858,24 +857,46 @@ SimpleTestCase
.. class:: SimpleTestCase() .. class:: SimpleTestCase()
A very thin subclass of :class:`unittest.TestCase`, it extends it with some A thin subclass of :class:`unittest.TestCase`, it extends it with some basic
basic functionality like: functionality like:
* Saving and restoring the Python warning machinery state. * Saving and restoring the Python warning machinery state.
* Checking that a callable :meth:`raises a certain exception <SimpleTestCase.assertRaisesMessage>`. * Some useful assertions like:
* :meth:`Testing form field rendering <SimpleTestCase.assertFieldOutput>`.
* Testing server :ref:`HTML responses for the presence/lack of a given fragment <assertions>`. * Checking that a callable :meth:`raises a certain exception
* The ability to run tests with :ref:`modified settings <overriding-settings>` <SimpleTestCase.assertRaisesMessage>`.
* Testing form field :meth:`rendering and error treatment
<SimpleTestCase.assertFieldOutput>`.
* Testing :meth:`HTML responses for the presence/lack of a given fragment
<SimpleTestCase.assertContains>`.
* Verifying that a template :meth:`has/hasn't been used to generate a given
response content <SimpleTestCase.assertTemplateUsed>`.
* Verifying a HTTP :meth:`redirect <SimpleTestCase.assertRedirects>` is
performed by the app.
* Robustly testing two :meth:`HTML fragments <SimpleTestCase.assertHTMLEqual>`
for equality/inequality or :meth:`containment <SimpleTestCase.assertInHTML>`.
* Robustly testing two :meth:`XML fragments <SimpleTestCase.assertXMLEqual>`
for equality/inequality.
* Robustly testing two :meth:`JSON fragments <SimpleTestCase.assertJSONEqual>`
for equality.
* The ability to run tests with :ref:`modified settings <overriding-settings>`.
* Using the :attr:`~SimpleTestCase.client` :class:`~django.test.client.Client`.
* Custom test-time :attr:`URL maps <SimpleTestCase.urls>`.
.. versionchanged:: 1.6
The latter two features were moved from ``TransactionTestCase`` to
``SimpleTestCase`` in Django 1.6.
If you need any of the other more complex and heavyweight Django-specific If you need any of the other more complex and heavyweight Django-specific
features like: features like:
* Using the :attr:`~TestCase.client` :class:`~django.test.client.Client`.
* Testing or using the ORM. * Testing or using the ORM.
* Database :attr:`~TestCase.fixtures`. * Database :attr:`~TransactionTestCase.fixtures`.
* Custom test-time :attr:`URL maps <TestCase.urls>`.
* Test :ref:`skipping based on database backend features <skipping-tests>`. * Test :ref:`skipping based on database backend features <skipping-tests>`.
* The remaining specialized :ref:`assert* <assertions>` methods. * The remaining specialized :meth:`assert*
<TransactionTestCase.assertQuerysetEqual>` methods.
then you should use :class:`~django.test.TransactionTestCase` or then you should use :class:`~django.test.TransactionTestCase` or
:class:`~django.test.TestCase` instead. :class:`~django.test.TestCase` instead.
@ -1137,9 +1158,9 @@ Test cases features
Default test client Default test client
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
.. attribute:: TestCase.client .. attribute:: SimpleTestCase.client
Every test case in a ``django.test.TestCase`` instance has access to an Every test case in a ``django.test.*TestCase`` instance has access to an
instance of a Django test client. This client can be accessed as instance of a Django test client. This client can be accessed as
``self.client``. This client is recreated for each test, so you don't have to ``self.client``. This client is recreated for each test, so you don't have to
worry about state (such as cookies) carrying over from one test to another. worry about state (such as cookies) carrying over from one test to another.
@ -1176,10 +1197,10 @@ This means, instead of instantiating a ``Client`` in each test::
Customizing the test client Customizing the test client
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. attribute:: TestCase.client_class .. attribute:: SimpleTestCase.client_class
If you want to use a different ``Client`` class (for example, a subclass If you want to use a different ``Client`` class (for example, a subclass
with customized behavior), use the :attr:`~TestCase.client_class` class with customized behavior), use the :attr:`~SimpleTestCase.client_class` class
attribute:: attribute::
from django.test import TestCase from django.test import TestCase
@ -1200,11 +1221,12 @@ attribute::
Fixture loading Fixture loading
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
.. attribute:: TestCase.fixtures .. attribute:: TransactionTestCase.fixtures
A test case for a database-backed Web site isn't much use if there isn't any A test case for a database-backed Web site isn't much use if there isn't any
data in the database. To make it easy to put test data into the database, data in the database. To make it easy to put test data into the database,
Django's custom ``TestCase`` class provides a way of loading **fixtures**. Django's custom ``TransactionTestCase`` class provides a way of loading
**fixtures**.
A fixture is a collection of data that Django knows how to import into a A fixture is a collection of data that Django knows how to import into a
database. For example, if your site has user accounts, you might set up a database. For example, if your site has user accounts, you might set up a
@ -1273,7 +1295,7 @@ or by the order of test execution.
URLconf configuration URLconf configuration
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
.. attribute:: TestCase.urls .. attribute:: SimpleTestCase.urls
If your application provides views, you may want to include tests that use the If your application provides views, you may want to include tests that use the
test client to exercise those views. However, an end user is free to deploy the test client to exercise those views. However, an end user is free to deploy the
@ -1282,9 +1304,9 @@ tests can't rely upon the fact that your views will be available at a
particular URL. particular URL.
In order to provide a reliable URL space for your test, In order to provide a reliable URL space for your test,
``django.test.TestCase`` provides the ability to customize the URLconf ``django.test.*TestCase`` classes provide the ability to customize the URLconf
configuration for the duration of the execution of a test suite. If your configuration for the duration of the execution of a test suite. If your
``TestCase`` instance defines an ``urls`` attribute, the ``TestCase`` will use ``*TestCase`` instance defines an ``urls`` attribute, the ``*TestCase`` will use
the value of that attribute as the :setting:`ROOT_URLCONF` for the duration the value of that attribute as the :setting:`ROOT_URLCONF` for the duration
of that test. of that test.
@ -1307,7 +1329,7 @@ URLconf for the duration of the test case.
Multi-database support Multi-database support
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
.. attribute:: TestCase.multi_db .. attribute:: TransactionTestCase.multi_db
Django sets up a test database corresponding to every database that is Django sets up a test database corresponding to every database that is
defined in the :setting:`DATABASES` definition in your settings defined in the :setting:`DATABASES` definition in your settings
@ -1340,12 +1362,12 @@ This test case will flush *all* the test databases before running
Overriding settings Overriding settings
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
.. method:: TestCase.settings .. method:: SimpleTestCase.settings
For testing purposes it's often useful to change a setting temporarily and For testing purposes it's often useful to change a setting temporarily and
revert to the original value after running the testing code. For this use case revert to the original value after running the testing code. For this use case
Django provides a standard Python context manager (see :pep:`343`) Django provides a standard Python context manager (see :pep:`343`)
:meth:`~django.test.TestCase.settings`, which can be used like this:: :meth:`~django.test.SimpleTestCase.settings`, which can be used like this::
from django.test import TestCase from django.test import TestCase
@ -1435,8 +1457,8 @@ MEDIA_ROOT, DEFAULT_FILE_STORAGE Default file storage
Emptying the test outbox Emptying the test outbox
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~
If you use Django's custom ``TestCase`` class, the test runner will clear the If you use any of Django's custom ``TestCase`` classes, the test runner will
contents of the test email outbox at the start of each test case. clear thecontents of the test email outbox at the start of each test case.
For more detail on email services during tests, see `Email services`_ below. For more detail on email services during tests, see `Email services`_ below.
@ -1486,31 +1508,7 @@ your test suite.
self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': [u'Enter a valid email address.']}) self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': [u'Enter a valid email address.']})
.. method:: SimpleTestCase.assertFormError(response, form, field, errors, msg_prefix='')
.. method:: TestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)
Asserts that a ``Response`` instance produced the given ``status_code`` and
that ``text`` appears in the content of the response. If ``count`` is
provided, ``text`` must occur exactly ``count`` times in the response.
Set ``html`` to ``True`` to handle ``text`` as HTML. The comparison with
the response content will be based on HTML semantics instead of
character-by-character equality. Whitespace is ignored in most cases,
attribute ordering is not significant. See
:meth:`~SimpleTestCase.assertHTMLEqual` for more details.
.. method:: TestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)
Asserts that a ``Response`` instance produced the given ``status_code`` and
that ``text`` does not appears in the content of the response.
Set ``html`` to ``True`` to handle ``text`` as HTML. The comparison with
the response content will be based on HTML semantics instead of
character-by-character equality. Whitespace is ignored in most cases,
attribute ordering is not significant. See
:meth:`~SimpleTestCase.assertHTMLEqual` for more details.
.. method:: TestCase.assertFormError(response, form, field, errors, msg_prefix='')
Asserts that a field on a form raises the provided list of errors when Asserts that a field on a form raises the provided list of errors when
rendered on the form. rendered on the form.
@ -1525,7 +1523,30 @@ your test suite.
``errors`` is an error string, or a list of error strings, that are ``errors`` is an error string, or a list of error strings, that are
expected as a result of form validation. expected as a result of form validation.
.. method:: TestCase.assertTemplateUsed(response, template_name, msg_prefix='') .. method:: SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)
Asserts that a ``Response`` instance produced the given ``status_code`` and
that ``text`` appears in the content of the response. If ``count`` is
provided, ``text`` must occur exactly ``count`` times in the response.
Set ``html`` to ``True`` to handle ``text`` as HTML. The comparison with
the response content will be based on HTML semantics instead of
character-by-character equality. Whitespace is ignored in most cases,
attribute ordering is not significant. See
:meth:`~SimpleTestCase.assertHTMLEqual` for more details.
.. method:: SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)
Asserts that a ``Response`` instance produced the given ``status_code`` and
that ``text`` does not appears in the content of the response.
Set ``html`` to ``True`` to handle ``text`` as HTML. The comparison with
the response content will be based on HTML semantics instead of
character-by-character equality. Whitespace is ignored in most cases,
attribute ordering is not significant. See
:meth:`~SimpleTestCase.assertHTMLEqual` for more details.
.. method:: SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='')
Asserts that the template with the given name was used in rendering the Asserts that the template with the given name was used in rendering the
response. response.
@ -1539,15 +1560,15 @@ your test suite.
with self.assertTemplateUsed(template_name='index.html'): with self.assertTemplateUsed(template_name='index.html'):
render_to_string('index.html') render_to_string('index.html')
.. method:: TestCase.assertTemplateNotUsed(response, template_name, msg_prefix='') .. method:: SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')
Asserts that the template with the given name was *not* used in rendering Asserts that the template with the given name was *not* used in rendering
the response. the response.
You can use this as a context manager in the same way as You can use this as a context manager in the same way as
:meth:`~TestCase.assertTemplateUsed`. :meth:`~SimpleTestCase.assertTemplateUsed`.
.. method:: TestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='') .. method:: SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='')
Asserts that the response return a ``status_code`` redirect status, it Asserts that the response return a ``status_code`` redirect status, it
redirected to ``expected_url`` (including any GET data), and the final redirected to ``expected_url`` (including any GET data), and the final
@ -1557,44 +1578,6 @@ your test suite.
``target_status_code`` will be the url and status code for the final ``target_status_code`` will be the url and status code for the final
point of the redirect chain. point of the redirect chain.
.. method:: TestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True)
Asserts that a queryset ``qs`` returns a particular list of values ``values``.
The comparison of the contents of ``qs`` and ``values`` is performed using
the function ``transform``; by default, this means that the ``repr()`` of
each value is compared. Any other callable can be used if ``repr()`` doesn't
provide a unique or helpful comparison.
By default, the comparison is also ordering dependent. If ``qs`` doesn't
provide an implicit ordering, you can set the ``ordered`` parameter to
``False``, which turns the comparison into a Python set comparison.
.. versionchanged:: 1.6
The method now checks for undefined order and raises ``ValueError``
if undefined order is spotted. The ordering is seen as undefined if
the given ``qs`` isn't ordered and the comparison is against more
than one ordered values.
.. method:: TestCase.assertNumQueries(num, func, *args, **kwargs)
Asserts that when ``func`` is called with ``*args`` and ``**kwargs`` that
``num`` database queries are executed.
If a ``"using"`` key is present in ``kwargs`` it is used as the database
alias for which to check the number of queries. If you wish to call a
function with a ``using`` parameter you can do it by wrapping the call with
a ``lambda`` to add an extra parameter::
self.assertNumQueries(7, lambda: my_function(using=7))
You can also use this as a context manager::
with self.assertNumQueries(2):
Person.objects.create(name="Aaron")
Person.objects.create(name="Daniel")
.. method:: SimpleTestCase.assertHTMLEqual(html1, html2, msg=None) .. method:: SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)
Asserts that the strings ``html1`` and ``html2`` are equal. The comparison Asserts that the strings ``html1`` and ``html2`` are equal. The comparison
@ -1624,6 +1607,8 @@ your test suite.
``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be
raised if one of them cannot be parsed. raised if one of them cannot be parsed.
Output in case of error can be customized with the ``msg`` argument.
.. method:: SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None) .. method:: SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)
Asserts that the strings ``html1`` and ``html2`` are *not* equal. The Asserts that the strings ``html1`` and ``html2`` are *not* equal. The
@ -1633,6 +1618,8 @@ your test suite.
``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be
raised if one of them cannot be parsed. raised if one of them cannot be parsed.
Output in case of error can be customized with the ``msg`` argument.
.. method:: SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None) .. method:: SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)
.. versionadded:: 1.5 .. versionadded:: 1.5
@ -1644,6 +1631,8 @@ your test suite.
syntax differences. When unvalid XML is passed in any parameter, an syntax differences. When unvalid XML is passed in any parameter, an
``AssertionError`` is always raised, even if both string are identical. ``AssertionError`` is always raised, even if both string are identical.
Output in case of error can be customized with the ``msg`` argument.
.. method:: SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None) .. method:: SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)
.. versionadded:: 1.5 .. versionadded:: 1.5
@ -1652,6 +1641,68 @@ your test suite.
comparison is based on XML semantics. See comparison is based on XML semantics. See
:meth:`~SimpleTestCase.assertXMLEqual` for details. :meth:`~SimpleTestCase.assertXMLEqual` for details.
Output in case of error can be customized with the ``msg`` argument.
.. method:: SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')
.. versionadded:: 1.5
Asserts that the HTML fragment ``needle`` is contained in the ``haystack`` one.
If the ``count`` integer argument is specified, then additionally the number
of ``needle`` occurrences will be strictly verified.
Whitespace in most cases is ignored, and attribute ordering is not
significant. The passed-in arguments must be valid HTML.
.. method:: SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)
.. versionadded:: 1.5
Asserts that the JSON fragments ``raw`` and ``expected_data`` are equal.
Usual JSON non-significant whitespace rules apply as the heavyweight is
delegated to the :mod:`json` library.
Output in case of error can be customized with the ``msg`` argument.
.. method:: TransactionTestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True)
Asserts that a queryset ``qs`` returns a particular list of values ``values``.
The comparison of the contents of ``qs`` and ``values`` is performed using
the function ``transform``; by default, this means that the ``repr()`` of
each value is compared. Any other callable can be used if ``repr()`` doesn't
provide a unique or helpful comparison.
By default, the comparison is also ordering dependent. If ``qs`` doesn't
provide an implicit ordering, you can set the ``ordered`` parameter to
``False``, which turns the comparison into a Python set comparison.
.. versionchanged:: 1.6
The method now checks for undefined order and raises ``ValueError``
if undefined order is spotted. The ordering is seen as undefined if
the given ``qs`` isn't ordered and the comparison is against more
than one ordered values.
.. method:: TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)
Asserts that when ``func`` is called with ``*args`` and ``**kwargs`` that
``num`` database queries are executed.
If a ``"using"`` key is present in ``kwargs`` it is used as the database
alias for which to check the number of queries. If you wish to call a
function with a ``using`` parameter you can do it by wrapping the call with
a ``lambda`` to add an extra parameter::
self.assertNumQueries(7, lambda: my_function(using=7))
You can also use this as a context manager::
with self.assertNumQueries(2):
Person.objects.create(name="Aaron")
Person.objects.create(name="Daniel")
.. _topics-testing-email: .. _topics-testing-email:
Email services Email services
@ -1701,7 +1752,7 @@ and contents::
self.assertEqual(mail.outbox[0].subject, 'Subject here') self.assertEqual(mail.outbox[0].subject, 'Subject here')
As noted :ref:`previously <emptying-test-outbox>`, the test outbox is emptied As noted :ref:`previously <emptying-test-outbox>`, the test outbox is emptied
at the start of every test in a Django ``TestCase``. To empty the outbox at the start of every test in a Django ``*TestCase``. To empty the outbox
manually, assign the empty list to ``mail.outbox``:: manually, assign the empty list to ``mail.outbox``::
from django.core import mail from django.core import mail