diff options
| author | Jeremy Dunck <jdunck@gmail.com> | 2007-06-18 16:48:27 +0000 |
|---|---|---|
| committer | Jeremy Dunck <jdunck@gmail.com> | 2007-06-18 16:48:27 +0000 |
| commit | bdcc95e5cce2754d78055f86d561ba2be92ba854 (patch) | |
| tree | 08a7e4c86244cb42fe577aec5c03a57b023822c2 /docs/testing.txt | |
| parent | 48c9f87e1f3ba9523d79c09f52c0ccc6221f08bf (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.txt | 273 |
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. |
