summaryrefslogtreecommitdiff
path: root/docs/testing.txt
diff options
context:
space:
mode:
authorJeremy Dunck <jdunck@gmail.com>2007-06-18 16:48:27 +0000
committerJeremy Dunck <jdunck@gmail.com>2007-06-18 16:48:27 +0000
commitbdcc95e5cce2754d78055f86d561ba2be92ba854 (patch)
tree08a7e4c86244cb42fe577aec5c03a57b023822c2 /docs/testing.txt
parent48c9f87e1f3ba9523d79c09f52c0ccc6221f08bf (diff)
gis: Merged revisions 4786-5490 via svnmerge from
http://code.djangoproject.com/svn/django/trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5492 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'docs/testing.txt')
-rw-r--r--docs/testing.txt273
1 files changed, 212 insertions, 61 deletions
diff --git a/docs/testing.txt b/docs/testing.txt
index 31cea791d3..50c4ec3046 100644
--- a/docs/testing.txt
+++ b/docs/testing.txt
@@ -2,19 +2,29 @@
Testing Django applications
===========================
-Automated testing is an extremely useful weapon in the bug-killing arsenal
-of the modern developer. When initially writing code, a test suite can be
-used to validate that code behaves as expected. When refactoring or
-modifying code, tests serve as a guide to ensure that behavior hasn't
-changed unexpectedly as a result of the refactor.
+Automated testing is an extremely useful bug-killing tool for the modern
+Web developer. You can use a collection of tests -- a **test suite** -- to
+solve, or avoid, a number of problems:
-Testing a web application is a complex task, as there are many
-components of a web application that must be validated and tested. To
-help you test your application, Django provides a test execution
-framework, and range of utilities that can be used to simulate and
-inspect various facets of a web application.
+ * When you're writing new code, you can use tests to validate your code
+ works as expected.
- This testing framework is currently under development, and may change
+ * When you're refactoring or modifying old code, you can use tests to
+ ensure your changes haven't affected your application's behavior
+ unexpectedly.
+
+Testing a Web application is a complex task, because a Web application is made
+of several layers of logic -- from HTTP-level request handling, to form
+validation and processing, to template rendering. With Django's test-execution
+framework and assorted utilities, you can simulate requests, insert test data,
+inspect your application's output and generally verify your code is doing what
+it should be doing.
+
+The best part is, it's really easy.
+
+.. admonition:: Note
+
+ This testing framework is currently under development. It may change
slightly before the next official Django release.
(That's *no* excuse not to write tests, though!)
@@ -137,7 +147,7 @@ doctests or unit tests are right for you.
If you've been using Python for a while, ``doctest`` will probably feel more
"pythonic". It's designed to make writing tests as easy as possible, so
there's no overhead of writing classes or methods; you simply put tests in
-docstrings. This gives the added advantage of given your modules automatic
+docstrings. This gives the added advantage of giving your modules automatic
documentation -- well-written doctests can kill both the documentation and the
testing bird with a single stone.
@@ -166,7 +176,8 @@ To assist in testing various features of your application, Django provides
tools that can be used to establish tests and test conditions.
* `Test Client`_
-* Fixtures_
+* `TestCase`_
+* `E-mail services`_
Test Client
-----------
@@ -216,21 +227,21 @@ can be invoked on the ``Client`` instance.
``post(path, data={}, content_type=MULTIPART_CONTENT)``
Make a POST request on the provided ``path``. If you provide a content type
- (e.g., ``text/xml`` for an XML payload), the contents of ``data`` will be
- sent as-is in the POST request, using the content type in the HTTP
+ (e.g., ``text/xml`` for an XML payload), the contents of ``data`` will be
+ sent as-is in the POST request, using the content type in the HTTP
``Content-Type`` header.
-
- If you do not provide a value for ``content_type``, the values in
+
+ If you do not provide a value for ``content_type``, the values in
``data`` will be transmitted with a content type of ``multipart/form-data``.
The key-value pairs in the data dictionary will be encoded as a multipart
message and used to create the POST data payload.
-
- To submit multiple values for a given key (for example, to specify
- the selections for a multiple selection list), provide the values as a
+
+ To submit multiple values for a given key (for example, to specify
+ the selections for a multiple selection list), provide the values as a
list or tuple for the required key. For example, a data dictionary of
``{'choices': ('a','b','d')}`` would submit three selected rows for the
field named ``choices``.
-
+
Submitting files is a special case. To POST a file, you need only
provide the file field name as a key, and a file handle to the file you wish to
upload as a value. The Test Client will populate the two POST fields (i.e.,
@@ -246,29 +257,42 @@ can be invoked on the ``Client`` instance.
file name), and `attachment_file` (containing the file data). Note that you
need to manually close the file after it has been provided to the POST.
-``login(path, username, password)``
- In a production site, it is likely that some views will be protected with
- the @login_required decorator provided by ``django.contrib.auth``. Interacting
- with a URL that has been login protected is a slightly complex operation,
- so the Test Client provides a simple method to automate the login process. A
- call to ``login()`` stimulates the series of GET and POST calls required
- to log a user into a @login_required protected view.
+``login(**credentials)``
+ **New in Django development version**
- If login is possible, the final return value of ``login()`` is the response
- that is generated by issuing a GET request on the protected URL. If login
- is not possible, ``login()`` returns False.
+ On a production site, it is likely that some views will be protected from
+ anonymous access through the use of the @login_required decorator, or some
+ other login checking mechanism. The ``login()`` method can be used to
+ simulate the effect of a user logging into the site. As a result of calling
+ this method, the Client will have all the cookies and session data required
+ to pass any login-based tests that may form part of a view.
+
+ In most cases, the ``credentials`` required by this method are the username
+ and password of the user that wants to log in, provided as keyword
+ arguments::
+
+ c = Client()
+ c.login(username='fred', password='secret')
+ # Now you can access a login protected view
+
+ If you are using a different authentication backend, this method may
+ require different credentials.
+
+ ``login()`` returns ``True`` if it the credentials were accepted and login
+ was successful.
Note that since the test suite will be executed using the test database,
- which contains no users by default. As a result, logins for your production
- site will not work. You will need to create users as part of the test suite
- to be able to test logins to your application.
+ which contains no users by default. As a result, logins that are valid
+ on your production site will not work under test conditions. You will
+ need to create users as part of the test suite (either manually, or
+ using a test fixture).
Testing Responses
~~~~~~~~~~~~~~~~~
-The ``get()``, ``post()`` and ``login()`` methods all return a Response
-object. This Response object has the following properties that can be used
-for testing purposes:
+The ``get()`` and ``post()`` methods both return a Response object. This
+Response object has the following properties that can be used for testing
+purposes:
=============== ==========================================================
Property Description
@@ -276,7 +300,7 @@ for testing purposes:
``status_code`` The HTTP status of the response. See RFC2616_ for a
full list of HTTP status codes.
- ``content`` The body of the response. The is the final page
+ ``content`` The body of the response. This is the final page
content as rendered by the view, or any error message
(such as the URL for a 302 redirect).
@@ -357,9 +381,31 @@ The following is a simple unit test using the Test Client::
# Check that the rendered context contains 5 customers
self.failUnlessEqual(len(response.context['customers']), 5)
-Fixtures
+TestCase
--------
+Normal python unit tests extend a base class of ``unittest.testCase``.
+Django provides an extension of this base class - ``django.test.TestCase``
+- that provides some additional capabilities that can be useful for
+testing web sites.
+
+Moving from a normal unittest TestCase to a Django TestCase is easy - just
+change the base class of your test from ``unittest.TestCase`` to
+``django.test.TestCase``. All of the standard Python unit test facilities
+will continue to be available, but they will be augmented with some useful
+extra facilities.
+
+Default Test Client
+~~~~~~~~~~~~~~~~~~~
+**New in Django development version**
+
+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
+``self.client``. This client is recreated for each test.
+
+Fixture loading
+~~~~~~~~~~~~~~~
+
A test case for a database-backed website isn't much use if there isn't any
data in the database. To make it easy to put test data into the database,
Django provides a fixtures framework.
@@ -370,22 +416,20 @@ comprise the fixture can be distributed over multiple directories, in
multiple applications.
.. note::
- If you have synchronized a Django project, you have already experienced
+ If you have synchronized a Django project, you have already experienced
the use of one fixture -- the ``initial_data`` fixture. Every time you
synchronize the database, Django installs the ``initial_data`` fixture.
This provides a mechanism to populate a new database with any initial
data (such as a default set of categories). Fixtures with other names
- can be installed manually using ``django-admin.py loaddata``.
-
+ can be installed manually using ``django-admin.py loaddata``.
-However, for the purposes of unit testing, each test must be able to
+However, for the purposes of unit testing, each test must be able to
guarantee the contents of the database at the start of each and every
-test. To do this, Django provides a TestCase baseclass that can integrate
-with fixtures.
+test.
-Moving from a normal unittest TestCase to a Django TestCase is easy - just
-change the base class of your test, and define a list of fixtures
-to be used. For example, the test case from `Writing unittests`_ would
+To define a fixture for a test, all you need to do is add a class
+attribute to your test describing the fixtures you want the test to use.
+For example, the test case from `Writing unittests`_ would
look like::
from django.test import TestCase
@@ -393,23 +437,116 @@ look like::
class AnimalTestCase(TestCase):
fixtures = ['mammals.json', 'birds']
-
+
def setUp(self):
# test definitions as before
At the start of each test case, before ``setUp()`` is run, Django will
-flush the database, returning the database the state it was in directly
-after ``syncdb`` was called. Then, all the named fixtures are installed.
+flush the database, returning the database the state it was in directly
+after ``syncdb`` was called. Then, all the named fixtures are installed.
In this example, any JSON fixture called ``mammals``, and any fixture
-named ``birds`` will be installed. See the documentation on
+named ``birds`` will be installed. See the documentation on
`loading fixtures`_ for more details on defining and installing fixtures.
-.. _`loading fixtures`: ../django_admin/#loaddata-fixture-fixture
+.. _`loading fixtures`: ../django-admin/#loaddata-fixture-fixture
-This flush/load procedure is repeated for each test in the test case, so you
-can be certain that the outcome of a test will not be affected by
+This flush/load procedure is repeated for each test in the test case, so you
+can be certain that the outcome of a test will not be affected by
another test, or the order of test execution.
+Emptying the test outbox
+~~~~~~~~~~~~~~~~~~~~~~~~
+**New in Django development version**
+
+At the start of each test case, in addition to installing fixtures,
+Django clears the contents of the test e-mail outbox.
+
+For more detail on e-mail services during tests, see `E-mail services`_.
+
+Assertions
+~~~~~~~~~~
+**New in Django development version**
+
+Normal Python unit tests have a wide range of assertions, such as
+``assertTrue`` and ``assertEquals`` that can be used to validate behavior.
+``django.TestCase`` adds to these, providing some assertions
+that can be useful in testing the behavior of web sites.
+
+``assertContains(response, text, count=1, status_code=200)``
+ Assert that a response indicates that a page could be retrieved and
+ produced the nominated status code, and that ``text`` occurs ``count``
+ times in the content of the response.
+
+``assertFormError(response, form, field, errors)``
+ Assert that a field on a form raised the provided list of errors when
+ rendered on the form.
+
+ ``form`` is the name the form object was given in the template context.
+
+ ``field`` is the name of the field on the form to check. If ``field``
+ has a value of ``None``, non-field errors will be checked.
+
+ ``errors`` is an error string, or a list of error strings, that are
+ expected as a result of form validation.
+
+``assertTemplateNotUsed(response, template_name)``
+ Assert that the template with the given name was *not* used in rendering
+ the response.
+
+``assertRedirects(response, expected_path, status_code=302, target_status_code=200)``
+ Assert that the response received produced the nominated status code,
+ redirects the browser to the provided path, and that retrieving the provided
+ path yields a response with the target status code.
+
+``assertTemplateUsed(response, template_name)``
+ Assert that the template with the given name was used in rendering the
+ response.
+
+E-mail services
+---------------
+
+**New in Django development version**
+
+If your view makes use of the `Django e-mail services`_, you don't really
+want e-mail to be sent every time you run a test using that view.
+
+When the Django test framework is initialized, it transparently replaces the
+normal `SMTPConnection`_ class with a dummy implementation that redirects all
+e-mail to a dummy outbox. This outbox, stored as ``django.core.mail.outbox``,
+is a simple list of all `EmailMessage`_ instances that have been sent.
+For example, during test conditions, it would be possible to run the following
+code::
+
+ from django.core import mail
+
+ # Send message
+ mail.send_mail('Subject here', 'Here is the message.', 'from@example.com',
+ ['to@example.com'], fail_silently=False)
+
+ # One message has been sent
+ self.assertEqual(len(mail.outbox), 1)
+ # Subject of first message is correct
+ self.assertEqual(mail.outbox[0].subject, 'Subject here')
+
+The ``mail.outbox`` object does not exist under normal execution conditions.
+The outbox is created during test setup, along with the dummy `SMTPConnection`_.
+When the test framework is torn down, the standard `SMTPConnection`_ class
+is restored, and the test outbox is destroyed.
+
+As noted `previously`_, the test outbox is emptied at the start of every
+test in a Django TestCase. To empty the outbox manually, assign the empty list
+to mail.outbox::
+
+ from django.core import mail
+
+ # Empty the test outbox
+ mail.outbox = []
+
+.. _`Django e-mail services`: ../email/
+.. _`SMTPConnection`: ../email/#the-emailmessage-and-smtpconnection-classes
+.. _`EmailMessage`: ../email/#the-emailmessage-and-smtpconnection-classes
+.. _`previously`: #emptying-the-test-outbox
+
Running tests
=============
@@ -434,6 +571,18 @@ database settings will the same as they would be for the project normally.
If you wish to use a name other than the default for the test database,
you can use the ``TEST_DATABASE_NAME`` setting to provide a name.
+**New in Django development version:** For fine-grained control over the
+character encoding of your database, use the ``TEST_DATABASE_CHARSET`` setting.
+If you're using MySQL, you can also use the ``TEST_DATABASE_COLLATION`` setting
+to control the particular collation used by the test database. See the
+settings_ documentation for details of these advanced settings.
+
+.. _settings: ../settings/
+
+The test database is created by the user in the ``DATABASE_USER`` setting.
+This user needs to have sufficient privileges to create a new database on the
+system.
+
Once the test database has been established, Django will run your tests.
If everything goes well, at the end you'll see::
@@ -468,10 +617,11 @@ failed::
FAILED (failures=1)
-The return code for the script will indicate the number of tests that failed.
+The return code for the script is the total number of failed and erroneous
+tests. If all the tests pass, the return code is 0.
Regardless of whether the tests pass or fail, the test database is destroyed when
-all the tests have been executed.
+all the tests have been executed.
Using a different testing framework
===================================
@@ -482,7 +632,7 @@ it does provide a mechanism to allow you to invoke tests constructed for
an alternative framework as if they were normal Django tests.
When you run ``./manage.py test``, Django looks at the ``TEST_RUNNER``
-setting to determine what to do. By default, ``TEST_RUNNER`` points to
+setting to determine what to do. By default, ``TEST_RUNNER`` points to
``django.test.simple.run_tests``. This method defines the default Django
testing behavior. This behavior involves:
@@ -512,7 +662,7 @@ arguments:
Verbosity determines the amount of notification and debug information that
will be printed to the console; `0` is no output, `1` is normal output,
and `2` is verbose output.
-
+
This method should return the number of tests that failed.
Testing utilities
@@ -523,11 +673,12 @@ a number of utility methods in the ``django.test.utils`` module.
``setup_test_environment()``
Performs any global pre-test setup, such as the installing the
- instrumentation of the template rendering system.
+ instrumentation of the template rendering system and setting up
+ the dummy SMTPConnection.
``teardown_test_environment()``
Performs any global post-test teardown, such as removing the instrumentation
- of the template rendering system.
+ of the template rendering system and restoring normal e-mail services.
``create_test_db(verbosity=1, autoclobber=False)``
Creates a new test database, and run ``syncdb`` against it.