diff options
Diffstat (limited to 'docs/testing.txt')
| -rw-r--r-- | docs/testing.txt | 438 |
1 files changed, 343 insertions, 95 deletions
diff --git a/docs/testing.txt b/docs/testing.txt index a0b8a8a187..50c4ec3046 100644 --- a/docs/testing.txt +++ b/docs/testing.txt @@ -2,21 +2,29 @@ Testing Django applications =========================== -**New in Django development version**. +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: -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. + * When you're writing new code, you can use tests to validate your code + works as expected. -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 stimulate and -inspect various facets of a web application. + * When you're refactoring or modifying old code, you can use tests to + ensure your changes haven't affected your application's behavior + unexpectedly. - This testing framework is currently under development, and may change +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!) @@ -92,7 +100,7 @@ Writing unittests Like doctests, Django's unit tests use a standard library module: unittest_. As with doctests, Django's test runner looks for any unit test cases defined -in ``models.py``, or in a ``tests.py`` file stored in the application +in ``models.py``, or in a ``tests.py`` file stored in the application directory. An equivalent unittest test case for the above example would look like:: @@ -111,8 +119,8 @@ An equivalent unittest test case for the above example would look like:: self.assertEquals(self.cat.speak(), 'The cat says "meow"') When you `run your tests`_, the test utility will find all the test cases -(that is, subclasses of ``unittest.TestCase``) in ``models.py`` and -``tests.py``, automatically build a test suite out of those test cases, +(that is, subclasses of ``unittest.TestCase``) in ``models.py`` and +``tests.py``, automatically build a test suite out of those test cases, and run that suite. For more details about ``unittest``, see the `standard library unittest @@ -139,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. @@ -164,12 +172,13 @@ in different circumstances. Testing Tools ============= -To assist in testing various features of your application, Django provides +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 ----------- @@ -178,36 +187,30 @@ GET and POST requests on a URL, and observe the response that is received. This allows you to test that the correct view is executed for a given URL, and that the view constructs the correct response. -As the response is generated, the Test Client gathers details on the +As the response is generated, the Test Client gathers details on the Template and Context objects that were used to generate the response. These Templates and Contexts are then provided as part of the response, and can be used as test conditions. .. admonition:: Test Client vs Browser Automation? - The Test Client is not intended as a replacement for Twill_, Selenium_, - or other browser automation frameworks - it is intended to allow - testing of the contexts and templates produced by a view, + The Test Client is not intended as a replacement for Twill_, Selenium_, + or other browser automation frameworks - it is intended to allow + testing of the contexts and templates produced by a view, rather than the HTML rendered to the end-user. - + A comprehensive test suite should use a combination of both: Test Client - tests to establish that the correct view is being called and that + tests to establish that the correct view is being called and that the view is collecting the correct context data, and Browser Automation tests to check that user interface behaves as expected. - + .. _Twill: http://twill.idyll.org/ .. _Selenium: http://www.openqa.org/selenium/ -The Test Client is stateful; if a cookie is returned as part of a response, -that cookie is provided as part of the next request issued to that Client -instance. Expiry policies for these cookies are not followed; if you want -a cookie to expire, either delete it manually from ``client.cookies``, or -create a new Client instance (which will effectively delete all cookies). - Making requests ~~~~~~~~~~~~~~~ -Creating an instance of ``Client`` (``django.test.client.Client``) requires +Creating an instance of ``Client`` (``django.test.client.Client``) requires no arguments at time of construction. Once constructed, the following methods can be invoked on the ``Client`` instance. @@ -220,104 +223,329 @@ can be invoked on the ``Client`` instance. will result in the evaluation of a GET request equivalent to:: - http://yoursite.com/customers/details/?name='fred'&age=7 + http://yoursite.com/customers/details/?name=fred&age=7 + +``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 + ``Content-Type`` header. -``post(path, data={})`` - Make a POST request on the provided ``path``. The key-value pairs in the - data dictionary will be used to create the POST data payload. This payload - will be transmitted with the mimetype ``multipart/form-data``. + 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. - However 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., - ``field`` and ``field_file``) required by FileField. For example:: + 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., + ``field`` and ``field_file``) required by Django's FileField. For example:: c = Client() f = open('wishlist.doc') c.post('/customers/wishes/', {'name':'fred', 'attachment':f}) f.close() - will result in the evaluation of a POST request on ``/customers/wishes/``, - with a POST dictionary that contains `name`, `attachment` (containing the + will result in the evaluation of a POST request on ``/customers/wishes/``, + with a POST dictionary that contains `name`, `attachment` (containing the 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 URL 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 URL 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 URL. +``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. - 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. + 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 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 =============== ========================================================== - ``status_code`` The HTTP status of the response. See RFC2616_ for a + ``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 as rendered by the view, or any error message + ``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). - ``template`` The Template instance that was used to render the final - content. Testing ``template.name`` can be particularly - useful; if the template was loaded from a file, - ``template.name`` will be the file name that was loaded. + ``template`` The Template instance that was used to render the final + content. Testing ``template.name`` can be particularly + useful; if the template was loaded from a file, + ``template.name`` will be the file name that was loaded. - If multiple templates were rendered, (e.g., if one - template includes another template),``template`` will - be a list of Template objects, in the order in which + If multiple templates were rendered, (e.g., if one + template includes another template),``template`` will + be a list of Template objects, in the order in which they were rendered. - ``context`` The Context that was used to render the template that + ``context`` The Context that was used to render the template that produced the response content. - As with ``template``, if multiple templates were rendered - ``context`` will be a list of Context objects, stored in - the order in which they were rendered. + As with ``template``, if multiple templates were rendered + ``context`` will be a list of Context objects, stored in + the order in which they were rendered. =============== ========================================================== .. _RFC2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html +Exceptions +~~~~~~~~~~ + +If you point the Test Client at a view that raises an exception, that exception +will be visible in the test case. You can then use a standard ``try...catch`` +block, or ``unittest.TestCase.assertRaises()`` to test for exceptions. + +The only exceptions that are not visible in a Test Case are ``Http404``, +``PermissionDenied`` and ``SystemExit``. Django catches these exceptions +internally and converts them into the appropriate HTTP responses codes. + +Persistent state +~~~~~~~~~~~~~~~~ + +The Test Client is stateful; if a cookie is returned as part of a response, +that cookie is provided as part of the next request issued by that Client +instance. Expiry policies for these cookies are not followed; if you want +a cookie to expire, either delete it manually or create a new Client +instance (which will effectively delete all cookies). + +There are two properties of the Test Client which are used to store persistent +state information. If necessary, these properties can be interrogated as +part of a test condition. + + =============== ========================================================== + Property Description + =============== ========================================================== + ``cookies`` A Python ``SimpleCookie`` object, containing the current + values of all the client cookies. + + ``session`` A dictionary-like object containing session information. + See the `session documentation`_ for full details. + =============== ========================================================== + +.. _`session documentation`: ../sessions/ + +Example +~~~~~~~ + The following is a simple unit test using the Test Client:: - + import unittest from django.test.client import Client - + class SimpleTest(unittest.TestCase): def setUp(self): # Every test needs a client self.client = Client() - def test_details(self): + def test_details(self): # Issue a GET request response = self.client.get('/customer/details/') - + # Check that the respose is 200 OK self.failUnlessEqual(response.status_code, 200) # Check that the rendered context contains 5 customers self.failUnlessEqual(len(response.context['customers']), 5) -Fixtures +TestCase -------- -Feature still to come... +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. + +A *Fixture* is a collection of files that contain the serialized contents of +the database. Each fixture has a unique name; however, the files that +comprise the fixture can be distributed over multiple directories, in +multiple applications. + +.. note:: + 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``. + +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 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 + from myapp.models import Animal + + 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. +In this example, any JSON fixture called ``mammals``, and any fixture +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 + +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 ============= @@ -335,13 +563,25 @@ but you only want to run the animals unit tests, run:: When you run your tests, you'll see a bunch of text flow by as the test database is created and models are initialized. This test database is -created from scratch every time you run your tests. +created from scratch every time you run your tests. -By default, the test database gets its name by prepending ``test_`` to -the database name specified by the ``DATABASE_NAME`` setting; all other +By default, the test database gets its name by prepending ``test_`` to +the database name specified by the ``DATABASE_NAME`` setting; all other 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. +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:: @@ -377,7 +617,11 @@ failed:: FAILED (failures=1) -When the tests have all been executed, the test database is destroyed. +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. Using a different testing framework =================================== @@ -388,7 +632,8 @@ 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 ``django.test.simple.run_tests``. This method defines the default Django +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: #. Performing global pre-test setup @@ -396,7 +641,7 @@ testing behavior. This behavior involves: #. Running ``syncdb`` to install models and initial data into the test database #. Looking for Unit Tests and Doctests in ``models.py`` and ``tests.py`` file for each installed application #. Running the Unit Tests and Doctests that are found -#. Destroying the test database. +#. Destroying the test database #. Performing global post-test teardown If you define your own test runner method and point ``TEST_RUNNER`` @@ -414,10 +659,12 @@ arguments: The module list is the list of Python modules that contain the models to be tested. This is the same format returned by ``django.db.models.get_apps()`` - Verbosity determines the amount of notification and debug information that - will be printed to the console; '0' is no output, '1' is normal output, + 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 ----------------- @@ -425,12 +672,13 @@ To assist in the creation of your own test runner, Django provides 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. + Performs any global pre-test setup, such as the installing the + 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. + Performs any global post-test teardown, such as removing the instrumentation + 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. |
