Changeset 5157
- Timestamp:
- 05/07/07 10:50:55 (1 year ago)
- Files:
-
- django/branches/boulder-oracle-sprint/django/db/models/query.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/django/test/client.py (modified) (5 diffs)
- django/branches/boulder-oracle-sprint/django/test/testcases.py (modified) (2 diffs)
- django/branches/boulder-oracle-sprint/docs/email.txt (modified) (6 diffs)
- django/branches/boulder-oracle-sprint/docs/testing.txt (modified) (10 diffs)
- django/branches/boulder-oracle-sprint/tests/modeltests/custom_columns/models.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/tests/modeltests/lookup/models.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/tests/modeltests/many_to_one/models.py (modified) (2 diffs)
- django/branches/boulder-oracle-sprint/tests/modeltests/reverse_lookup/models.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/tests/modeltests/test_client/models.py (modified) (9 diffs)
- django/branches/boulder-oracle-sprint/tests/modeltests/test_client/urls.py (modified) (2 diffs)
- django/branches/boulder-oracle-sprint/tests/modeltests/test_client/views.py (modified) (2 diffs)
- django/branches/boulder-oracle-sprint/tests/regressiontests/null_queries/models.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/tests/templates/base.html (copied) (copied from django/trunk/tests/templates/base.html)
- django/branches/boulder-oracle-sprint/tests/templates/form_view.html (copied) (copied from django/trunk/tests/templates/form_view.html)
- django/branches/boulder-oracle-sprint/tests/templates/login.html (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/branches/boulder-oracle-sprint/django/db/models/query.py
r5135 r5157 999 999 field_choices(current_opts.get_all_related_objects(), True) + \ 1000 1000 field_choices(current_opts.fields, False) 1001 raise TypeError, "Cannot resolve keyword '%s' into field , choices are: %s" % (name, ", ".join(choices))1001 raise TypeError, "Cannot resolve keyword '%s' into field. Choices are: %s" % (name, ", ".join(choices)) 1002 1002 1003 1003 # Check whether an intermediate join is required between current_table django/branches/boulder-oracle-sprint/django/test/client.py
r4777 r5157 1 import datetime 1 2 import sys 2 3 from cStringIO import StringIO 3 4 from urlparse import urlparse 4 5 from django.conf import settings 6 from django.contrib.auth import authenticate, login 7 from django.contrib.sessions.models import Session 8 from django.contrib.sessions.middleware import SessionWrapper 5 9 from django.core.handlers.base import BaseHandler 6 10 from django.core.handlers.wsgi import WSGIRequest 7 11 from django.core.signals import got_request_exception 8 12 from django.dispatch import dispatcher 9 from django.http import urlencode, SimpleCookie 13 from django.http import urlencode, SimpleCookie, HttpRequest 10 14 from django.test import signals 11 15 from django.utils.functional import curry … … 114 118 self.defaults = defaults 115 119 self.cookies = SimpleCookie() 116 self.session = {}117 120 self.exc_info = None 118 121 … … 124 127 self.exc_info = sys.exc_info() 125 128 129 def _session(self): 130 "Obtain the current session variables" 131 if 'django.contrib.sessions' in settings.INSTALLED_APPS: 132 cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None) 133 if cookie: 134 return SessionWrapper(cookie.value) 135 return {} 136 session = property(_session) 137 126 138 def request(self, **request): 127 139 """ … … 172 184 raise self.exc_info[1], None, self.exc_info[2] 173 185 174 # Update persistent cookie and sessiondata186 # Update persistent cookie data 175 187 if response.cookies: 176 188 self.cookies.update(response.cookies) 177 189 178 if 'django.contrib.sessions' in settings.INSTALLED_APPS:179 from django.contrib.sessions.middleware import SessionWrapper180 cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)181 if cookie:182 self.session = SessionWrapper(cookie.value)183 184 190 return response 185 191 … … 216 222 return self.request(**r) 217 223 218 def login(self, path, username, password, **extra): 219 """ 220 A specialized sequence of GET and POST to log into a view that 221 is protected by a @login_required access decorator. 222 223 path should be the URL of the page that is login protected. 224 225 Returns the response from GETting the requested URL after 226 login is complete. Returns False if login process failed. 227 """ 228 # First, GET the page that is login protected. 229 # This page will redirect to the login page. 230 response = self.get(path) 231 if response.status_code != 302: 224 def login(self, **credentials): 225 """Set the Client to appear as if it has sucessfully logged into a site. 226 227 Returns True if login is possible; False if the provided credentials 228 are incorrect, or if the Sessions framework is not available. 229 """ 230 user = authenticate(**credentials) 231 if user and 'django.contrib.sessions' in settings.INSTALLED_APPS: 232 obj = Session.objects.get_new_session_object() 233 234 # Create a fake request to store login details 235 request = HttpRequest() 236 request.session = SessionWrapper(obj.session_key) 237 login(request, user) 238 239 # Set the cookie to represent the session 240 self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key 241 self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None 242 self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/' 243 self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN 244 self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None 245 self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None 246 247 # Set the session values 248 Session.objects.save(obj.session_key, request.session._session, 249 datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE)) 250 251 return True 252 else: 232 253 return False 233 234 _, _, login_path, _, data, _= urlparse(response['Location']) 235 next = data.split('=')[1] 236 237 # Second, GET the login page; required to set up cookies 238 response = self.get(login_path, **extra) 239 if response.status_code != 200: 240 return False 241 242 # Last, POST the login data. 243 form_data = { 244 'username': username, 245 'password': password, 246 'next' : next, 247 } 248 response = self.post(login_path, data=form_data, **extra) 249 250 # Login page should 302 redirect to the originally requested page 251 if (response.status_code != 302 or 252 urlparse(response['Location'])[2] != path): 253 return False 254 255 # Since we are logged in, request the actual page again 256 return self.get(path) 254 django/branches/boulder-oracle-sprint/django/test/testcases.py
r4695 r5157 1 1 import re, doctest, unittest 2 from urlparse import urlparse 2 3 from django.db import transaction 3 4 from django.core import management 4 5 from django.db.models import get_apps 5 6 from django.test.client import Client 7 6 8 normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s) 7 9 … … 47 49 48 50 """ 51 self.client = Client() 49 52 self.install_fixtures() 50 53 super(TestCase, self).run(result) 54 55 def assertRedirects(self, response, expected_path): 56 """Assert that a response redirected to a specific URL, and that the 57 redirect URL can be loaded. 58 59 """ 60 self.assertEqual(response.status_code, 302, 61 "Response didn't redirect: Reponse code was %d" % response.status_code) 62 scheme, netloc, path, params, query, fragment = urlparse(response['Location']) 63 self.assertEqual(path, expected_path, 64 "Response redirected to '%s', expected '%s'" % (path, expected_path)) 65 redirect_response = self.client.get(path) 66 self.assertEqual(redirect_response.status_code, 200, 67 "Couldn't retrieve redirection page '%s'" % path) 68 69 def assertContains(self, response, text, count=1): 70 """Assert that a response indicates that a page was retreived successfully, 71 (i.e., the HTTP status code was 200), and that ``text`` occurs ``count`` 72 times in the content of the response. 73 74 """ 75 self.assertEqual(response.status_code, 200, 76 "Couldn't retrieve page'") 77 real_count = response.content.count(text) 78 self.assertEqual(real_count, count, 79 "Could only find %d of %d instances of '%s' in response" % (real_count, count, text)) 80 81 def assertFormError(self, response, form, field, errors): 82 "Assert that a form used to render the response has a specific field error" 83 if not response.context: 84 self.fail('Response did not use any contexts to render the response') 85 86 # If there is a single context, put it into a list to simplify processing 87 if not isinstance(response.context, list): 88 contexts = [response.context] 89 else: 90 contexts = response.context 91 92 # If a single error string is provided, make it a list to simplify processing 93 if not isinstance(errors, list): 94 errors = [errors] 95 96 # Search all contexts for the error. 97 found_form = False 98 for i,context in enumerate(contexts): 99 if form in context: 100 found_form = True 101 try: 102 for err in errors: 103 if field: 104 self.assertTrue(err in context[form].errors[field], 105 "The field '%s' on form '%s' in context %d does not contain the error '%s' (actual errors: %s)" % 106 (field, form, i, err, list(context[form].errors[field]))) 107 else: 108 self.assertTrue(err in context[form].non_field_errors(), 109 "The form '%s' in context %d does not contain the non-field error '%s' (actual errors: %s)" % 110 (form, i, err, list(context[form].non_field_errors()))) 111 except KeyError: 112 self.fail("The form '%s' in context %d does not contain the field '%s'" % (form, i, field)) 113 if not found_form: 114 self.fail("The form '%s' was not used to render the response" % form) 115 116 def assertTemplateUsed(self, response, template_name): 117 "Assert that the template with the provided name was used in rendering the response" 118 if isinstance(response.template, list): 119 template_names = [t.name for t in response.template] 120 self.assertTrue(template_name in template_names, 121 "Template '%s' was not one of the templates used to render the response. Templates used: %s" % 122 (template_name, template_names)) 123 elif response.template: 124 self.assertEqual(template_name, response.template.name, 125 "Template '%s' was not used to render the response. Actual template was '%s'" % 126 (template_name, response.template.name)) 127 else: 128 self.fail('No templates used to render the response') 129 130 def assertTemplateNotUsed(self, response, template_name): 131 "Assert that the template with the provided name was NOT used in rendering the response" 132 if isinstance(response.template, list): 133 self.assertFalse(template_name in [t.name for t in response.template], 134 "Template '%s' was used unexpectedly in rendering the response" % template_name) 135 elif response.template: 136 self.assertNotEqual(template_name, response.template.name, 137 "Template '%s' was used unexpectedly in rendering the response" % template_name) 138 django/branches/boulder-oracle-sprint/docs/email.txt
r5148 r5157 21 21 ['to@example.com'], fail_silently=False) 22 22 23 Mail will be sent using the SMTP host and port specified in the `EMAIL_HOST`_24 and`EMAIL_PORT`_ settings. The `EMAIL_HOST_USER`_ and `EMAIL_HOST_PASSWORD`_25 settings, if set, will be used to authenticate to the SMTP serverand the26 `EMAIL_USE_TLS`_ setting s will controlwhether a secure connection is used.23 Mail is sent using the SMTP host and port specified in the `EMAIL_HOST`_ and 24 `EMAIL_PORT`_ settings. The `EMAIL_HOST_USER`_ and `EMAIL_HOST_PASSWORD`_ 25 settings, if set, are used to authenticate to the SMTP server, and the 26 `EMAIL_USE_TLS`_ setting controls whether a secure connection is used. 27 27 28 28 .. note:: 29 29 30 The character set of e mail sent with ``django.core.mail`` will be set to30 The character set of e-mail sent with ``django.core.mail`` will be set to 31 31 the value of your `DEFAULT_CHARSET setting`_. 32 32 … … 37 37 .. _EMAIL_HOST_PASSWORD: ../settings/#email-host-password 38 38 .. _EMAIL_USE_TLS: ../settings/#email-use-tls 39 40 39 41 40 send_mail() … … 194 193 Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin 195 194 wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes 196 in ``django. mail``. If you ever need to customize the way Django sends email,197 you can subclass these two classes to suit your needs.195 in ``django.core.mail``. If you ever need to customize the way Django sends 196 e-mail, you can subclass these two classes to suit your needs. 198 197 199 198 .. note:: 200 199 Not all features of the ``EmailMessage`` class are available through the 201 200 ``send_mail()`` and related wrapper functions. If you wish to use advanced 202 features such as including BCC recipients or multi-part email, you will203 need tocreate ``EmailMessage`` instances directly.204 205 In general, ``EmailMessage`` is responsible for creating the e mail message201 features, such as BCC'ed recipients or multi-part e-mail, you'll need to 202 create ``EmailMessage`` instances directly. 203 204 In general, ``EmailMessage`` is responsible for creating the e-mail message 206 205 itself. ``SMTPConnection`` is responsible for the network connection side of 207 206 the operation. This means you can reuse the same connection (an 208 207 ``SMTPConnection`` instance) for multiple messages. 209 208 210 The ``EmailMessage`` class is initiali sed as follows::209 The ``EmailMessage`` class is initialized as follows:: 211 210 212 211 email = EmailMessage(subject, body, from_email, to, bcc, connection) … … 214 213 All of these parameters are optional. If ``from_email`` is omitted, the value 215 214 from ``settings.DEFAULT_FROM_EMAIL`` is used. Both the ``to`` and ``bcc`` 216 parameters are lists of addresses. 217 218 The class has the following methods that you can use: 219 220 * ``send()`` sends the message, using either the connection that is specified 221 in the ``connection`` attribute, or creating a new connection if none already 222 exists. 223 * ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a 224 sub-class of Python's ``email.MIMEText.MIMEText`` class) holding the 225 message to be sent. If you ever need to extend the `EmailMessage` class, 226 you will probably want to override this method to put the content you wish 227 into the MIME object. 228 * ``recipients()`` returns a lists of all the recipients of the message, 229 whether they are recorded in the ``to`` or ``bcc`` attributes. This is 230 another method you need to possibly override when sub-classing, since the 231 SMTP server needs to be told the full list of recipients when the message 232 is sent. If you add another way to specify recipients in your class, they 233 need to be returned from this method as well. 215 parameters are lists of addresses, as strings. 216 217 For example:: 218 219 email = EmailMessage('Hello', 'Body goes here', 'from@example.com', 220 ['to1@example.com', 'to2@example.com'], 221 ['bcc@example.com']) 222 223 The class has the following methods: 224 225 * ``send()`` sends the message, using either the connection that is 226 specified in the ``connection`` attribute, or creating a new connection 227 if none already exists. 228 229 * ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a 230 sub-class of Python's ``email.MIMEText.MIMEText`` class) holding the 231 message to be sent. If you ever need to extend the `EmailMessage` class, 232 you'll probably want to override this method to put the content you wish 233 into the MIME object. 234 235 * ``recipients()`` returns a list of all the recipients of the message, 236 whether they're recorded in the ``to`` or ``bcc`` attributes. This is 237 another method you might need to override when sub-classing, because the 238 SMTP server needs to be told the full list of recipients when the message 239 is sent. If you add another way to specify recipients in your class, they 240 need to be returned from this method as well. 234 241 235 242 The ``SMTPConnection`` class is initialized with the host, port, username and … … 237 244 options, they are read from your settings file. 238 245 239 If you are sending lots of messages at once, the ``send_messages()`` method of240 the ``SMTPConnection`` class will beuseful. It takes a list of ``EmailMessage``241 instances (or sub -classes) and sends them over a single connection. For242 example,if you have a function called ``get_notification_email()`` that returns a243 list of ``EmailMessage`` objects representing some periodic e mail you wish to246 If you're sending lots of messages at once, the ``send_messages()`` method of 247 the ``SMTPConnection`` class is useful. It takes a list of ``EmailMessage`` 248 instances (or subclasses) and sends them over a single connection. For example, 249 if you have a function called ``get_notification_email()`` that returns a 250 list of ``EmailMessage`` objects representing some periodic e-mail you wish to 244 251 send out, you could send this with:: 245 252 … … 247 254 messages = get_notification_email() 248 255 connection.send_messages(messages) 249 django/branches/boulder-oracle-sprint/docs/testing.txt
r5047 r5157 3 3 =========================== 4 4 5 Automated testing is an extremely useful weapon in the bug-killing arsenal 6 of the modern developer. When initially writing code, a test suite can be 7 used to validate that code behaves as expected. When refactoring or 8 modifying code, tests serve as a guide to ensure that behavior hasn't 9 changed unexpectedly as a result of the refactor. 10 11 Testing a web application is a complex task, as there are many 12 components of a web application that must be validated and tested. To 13 help you test your application, Django provides a test execution 14 framework, and range of utilities that can be used to simulate and 15 inspect various facets of a web application. 16 17 This testing framework is currently under development, and may change 5 Automated testing is an extremely useful bug-killing tool for the modern 6 Web developer. You can use a collection of tests -- a **test suite** -- to 7 to solve, or avoid, a number of problems: 8 9 * When you're writing new code, you can use tests to validate your code 10 works as expected. 11 12 * When you're refactoring or modifying old code, you can use tests to 13 ensure your changes haven't affected your application's behavior 14 unexpectedly. 15 16 Testing a Web application is a complex task, because a Web application is made 17 of several layers of logic -- from HTTP-level request handling, to form 18 validation and processing, to template rendering. With Django's test-execution 19 framework and assorted utilities, you can simulate requests, insert test data, 20 inspect your application's output and generally verify your code is doing what 21 it should be doing. 22 23 The best part is, it's really easy. 24 25 .. admonition:: Note 26 27 This testing framework is currently under development. It may change 18 28 slightly before the next official Django release. 19 29 … … 167 177 168 178 * `Test Client`_ 169 * Fixtures_179 * `TestCase`_ 170 180 171 181 Test Client … … 217 227 ``post(path, data={}, content_type=MULTIPART_CONTENT)`` 218 228 Make a POST request on the provided ``path``. If you provide a content type 219 (e.g., ``text/xml`` for an XML payload), the contents of ``data`` will be 220 sent as-is in the POST request, using the content type in the HTTP 229 (e.g., ``text/xml`` for an XML payload), the contents of ``data`` will be 230 sent as-is in the POST request, using the content type in the HTTP 221 231 ``Content-Type`` header. 222 223 If you do not provide a value for ``content_type``, the values in 232 233 If you do not provide a value for ``content_type``, the values in 224 234 ``data`` will be transmitted with a content type of ``multipart/form-data``. 225 235 The key-value pairs in the data dictionary will be encoded as a multipart 226 236 message and used to create the POST data payload. 227 228 To submit multiple values for a given key (for example, to specify 229 the selections for a multiple selection list), provide the values as a 237 238 To submit multiple values for a given key (for example, to specify 239 the selections for a multiple selection list), provide the values as a 230 240 list or tuple for the required key. For example, a data dictionary of 231 241 ``{'choices': ('a','b','d')}`` would submit three selected rows for the 232 242 field named ``choices``. 233 243 234 244 Submitting files is a special case. To POST a file, you need only 235 245 provide the file field name as a key, and a file handle to the file you wish to … … 247 257 need to manually close the file after it has been provided to the POST. 248 258 249 ``login(path, username, password)`` 250 In a production site, it is likely that some views will be protected with 251 the @login_required decorator provided by ``django.contrib.auth``. Interacting 252 with a URL that has been login protected is a slightly complex operation, 253 so the Test Client provides a simple method to automate the login process. A 254 call to ``login()`` stimulates the series of GET and POST calls required 255 to log a user into a @login_required protected view. 256 257 If login is possible, the final return value of ``login()`` is the response 258 that is generated by issuing a GET request on the protected URL. If login 259 is not possible, ``login()`` returns False. 259 ``login(**credentials)`` 260 ** New in Django development version ** 261 262 On a production site, it is likely that some views will be protected from 263 anonymous access through the use of the @login_required decorator, or some 264 other login checking mechanism. The ``login()`` method can be used to 265 simulate the effect of a user logging into the site. As a result of calling 266 this method, the Client will have all the cookies and session data required 267 to pass any login-based tests that may form part of a view. 268 269 In most cases, the ``credentials`` required by this method are the username 270 and password of the user that wants to log in, provided as keyword 271 arguments:: 272 273 c = Client() 274 c.login(username='fred', password='secret') 275 # Now you can access a login protected view 276 277 If you are using a different authentication backend, this method may 278 require different credentials. 279 280 ``login()`` returns ``True`` if it the credentials were accepted and login 281 was successful. 260 282 261 283 Note that since the test suite will be executed using the test database, 262 which contains no users by default. As a result, logins for your production 263 site will not work. You will need to create users as part of the test suite 264 to be able to test logins to your application. 284 which contains no users by default. As a result, logins that are valid 285 on your production site will not work under test conditions. You will 286 need to create users as part of the test suite (either manually, or 287 using a test fixture). 265 288 266 289 Testing Responses … … 358 381 self.failUnlessEqual(len(response.context['customers']), 5) 359 382 360 Fixtures 383 TestCase 361 384 -------- 385 386 Normal python unit tests extend a base class of ``unittest.testCase``. 387 Django provides an extension of this base class - ``django.test.TestCase`` 388 - that provides some additional capabilities that can be useful for 389 testing web sites. 390 391 Moving from a normal unittest TestCase to a Django TestCase is easy - just 392 change the base class of your test from ``unittest.TestCase`` to 393 ``django.test.TestCase``. All of the standard Python unit test facilities 394 will continue to be available, but they will be augmented with some useful 395 extra facilities. 396 397 Default Test Client 398 ~~~~~~~~~~~~~~~~~~~ 399 ** New in Django development version ** 400 401 Every test case in a ``django.test.TestCase`` instance has access to an 402 instance of a Django `Test Client`_. This Client can be accessed as 403 ``self.client``. This client is recreated for each test. 404 405 Fixture loading 406 ~~~~~~~~~~~~~~~ 362 407 363 408 A test case for a database-backed website isn't much use if there isn't any … … 371 416 372 417 .. note:: 373 If you have synchronized a Django project, you have already experienced 418 If you have synchronized a Django project, you have already experienced 374 419 the use of one fixture -- the ``initial_data`` fixture. Every time you 375 420 synchronize the database, Django installs the ``initial_data`` fixture. 376 421 This provides a mechanism to populate a new database with any initial 377 422 data (such as a default set of categories). Fixtures with other names 378 can be installed manually using ``django-admin.py loaddata``. 379 380 381 However, for the purposes of unit testing, each test must be able to 423 can be installed manually using ``django-admin.py loaddata``. 424 425 However, for the purposes of unit testing, each test must be able to 382 426 guarantee the contents of the database at the start of each and every 383 test. To do this, Django provides a TestCase baseclass that can integrate 384 with fixtures. 385 386 Moving from a normal unittest TestCase to a Django TestCase is easy - just 387 change the base class of your test, and define a list of fixtures 388 to be used. For example, the test case from `Writing unittests`_ would 427 test. 428 429 To define a fixture for a test, all you need to do is add a class 430 attribute to your test describing the fixtures you want the test to use. 431 For example, the test case from `Writing unittests`_ would 389 432 look like:: 390 433 … … 394 437 class AnimalTestCase(TestCase): 395 438 fixtures = ['mammals.json', 'birds'] 396 439 397 440 def setUp(self): 398 441 # test definitions as before 399 442 400 443 At the start of each test case, before ``setUp()`` is run, Django will 401 flush the database, returning the database the state it was in directly 402 after ``syncdb`` was called. Then, all the named fixtures are installed. 444 flush the database, returning the database the state it was in directly 445 after ``syncdb`` was called. Then, all the named fixtures are installed. 403 446 In this example, any JSON fixture called ``mammals``, and any fixture 404 named ``birds`` will be installed. See the documentation on 447 named ``birds`` will be installed. See the documentation on 405 448 `loading fixtures`_ for more details on defining and installing fixtures. 406 449 407 450 .. _`loading fixtures`: ../django-admin/#loaddata-fixture-fixture 408 451 409 This flush/load procedure is repeated for each test in the test case, so you 410 can be certain that the outcome of a test will not be affected by 452 This flush/load procedure is repeated for each test in the test case, so you 453 can be certain that the outcome of a test will not be affected by 411 454 another test, or the order of test execution. 412 455 456 Assertions 457 ~~~~~~~~~~ 458 ** New in Django development version ** 459 460 Normal Python unit tests have a wide range of assertions, such as 461 ``assertTrue`` and ``assertEquals`` that can be used to validate behavior. 462 ``django.TestCase`` adds to these, providing some assertions 463 that can be useful in testing the behavior of web sites. 464 465 ``assertContains(response, text, count=1)`` 466 Assert that a response indicates that a page was retrieved successfully, 467 (i.e., the HTTP status code was 200), and that ``text`` occurs ``count`` 468 times in the content of the response. 469 470 ``assertFormError(response, form, field, errors)`` 471 Assert that a field on a form raised the provided list of errors when 472 rendered on the form. 473 474 ``form`` is the name the form object was given in the template context. 475 476 ``field`` is the name of the field on the form to check. If ``field`` 477 has a value of ``None``, non-field errors will be checked. 478 479 ``errors`` is an error string, or a list of error strings, that are 480 expected as a result of form validation. 481 482 ``assertTemplateNotUsed(response, template_name)`` 483 Assert that the template with the given name was *not* used in rendering 484 the response. 485 486 ``assertRedirects(response, expected_path)`` 487 Assert that the response received redirects the browser to the provided 488 path, and that the expected_path can be retrieved. 489 490 ``assertTemplateUsed(response, template_name)`` 491 Assert that the template with the given name was used in rendering the 492 response. 493 494 413 495 Running tests 414 496 ============= … … 469 551 FAILED (failures=1) 470 552 471 The return code for the script is the total number of failed and erroneous 553 The return code for the script is the total number of failed and erroneous 472 554 tests. If all the tests pass, the return code is 0. 473 555 474 556 Regardless of whether the tests pass or fail, the test database is destroyed when 475 all the tests have been executed. 557 all the tests have been executed. 476 558 477 559 Using a different testing framework … … 484 566 485 567 When you run ``./manage.py test``, Django looks at the ``TEST_RUNNER`` 486 setting to determine what to do. By default, ``TEST_RUNNER`` points to 568 setting to determine what to do. By default, ``TEST_RUNNER`` points to 487 569 ``django.test.simple.run_tests``. This method defines the default Django 488 570 testing behavior. This behavior involves: … … 514 596 will be printed to the console; `0` is no output, `1` is normal output, 515 597 and `2` is verbose output. 516 598 517 599 This method should return the number of tests that failed. 518 600 django/branches/boulder-oracle-sprint/tests/modeltests/custom_columns/models.py
r5135 r5157 72 72 Traceback (most recent call last): 73 73 ... 74 TypeError: Cannot resolve keyword 'firstname' into field , choices are: article, id, first_name, last_name74 TypeError: Cannot resolve keyword 'firstname' into field. Choices are: article, id, first_name, last_name 75 75 76 76 >>> a = Author.objects.get(last_name__exact='Smith') django/branches/boulder-oracle-sprint/tests/modeltests/lookup/models.py
r5135 r5157 224 224 Traceback (most recent call last): 225 225 ... 226 TypeError: Cannot resolve keyword 'pub_date_year' into field , choices are: id, headline, pub_date226 TypeError: Cannot resolve keyword 'pub_date_year' into field. Choices are: id, headline, pub_date 227 227 228 228 >>> Article.objects.filter(headline__starts='Article') 229 229 Traceback (most recent call last): 230 230 ... 231 TypeError: Cannot resolve keyword 'headline__starts' into field , choices are: id, headline, pub_date231 TypeError: Cannot resolve keyword 'headline__starts' into field. Choices are: id, headline, pub_date 232 232 233 233 """} django/branches/boulder-oracle-sprint/tests/modeltests/many_to_one/models.py
r5135 r5157 175 175 Traceback (most recent call last): 176 176 ... 177 TypeError: Cannot resolve keyword 'reporter_id' into field , choices are: id, headline, pub_date, reporter177 TypeError: Cannot resolve keyword 'reporter_id' into field. Choices are: id, headline, pub_date, reporter 178 178 179 179 # You need to specify a comparison clause … … 181 181 Traceback (most recent call last): 182 182 ... 183 TypeError: Cannot resolve keyword 'reporter_id' into field , choices are: id, headline, pub_date, reporter183 TypeError: Cannot resolve keyword 'reporter_id' into field. Choices are: id, headline, pub_date, reporter 184 184 185 185 # You can also instantiate an Article by passing django/branches/boulder-oracle-sprint/tests/modeltests/reverse_lookup/models.py
r5135 r5157 56 56 Traceback (most recent call last): 57 57 ... 58 TypeError: Cannot resolve keyword 'choice' into field , choices are: poll_choice, related_choice, id, question, creator58 TypeError: Cannot resolve keyword 'choice' into field. Choices are: poll_choice, related_choice, id, question, creator 59 59 """} django/branches/boulder-oracle-sprint/tests/modeltests/test_client/models.py
r4841 r5157 25 25 fixtures = ['testdata.json'] 26 26 27 def setUp(self):28 "Set up test environment"29 self.client = Client()30 31 27 def test_get_view(self): 32 28 "GET a view" … … 34 30 35 31 # Check some response details 36 self.assert Equal(response.status_code, 200)32 self.assertContains(response, 'This is a test') 37 33 self.assertEqual(response.context['var'], 42) 38 34 self.assertEqual(response.template.name, 'GET Template') 39 self.failUnless('This is a test.' in response.content) 40 35 36 def test_no_template_view(self): 37 "Check that template usage assersions work then templates aren't in use" 38 response = self.client.get('/test_client/no_template_view/') 39 40 # Check that the no template case doesn't mess with the template assertions 41 self.assertTemplateNotUsed(response, 'GET Template') 42 41 43 def test_get_post_view(self): 42 44 "GET a view that normally expects POSTs" … … 46 48 self.assertEqual(response.status_code, 200) 47 49 self.assertEqual(response.template.name, 'Empty GET Template') 50 self.assertTemplateUsed(response, 'Empty GET Template') 51 self.assertTemplateNotUsed(response, 'Empty POST Template') 48 52 49 53 def test_empty_post(self): … … 54 58 self.assertEqual(response.status_code, 200) 55 59 self.assertEqual(response.template.name, 'Empty POST Template') 60 self.assertTemplateNotUsed(response, 'Empty GET Template') 61 self.assertTemplateUsed(response, 'Empty POST Template') 56 62 57 63 def test_post(self): … … 81 87 82 88 # Check that the response was a 302 (redirect) 83 self.assert Equal(response.status_code, 302)89 self.assertRedirects(response, '/test_client/get_view/') 84 90 85 91 def test_valid_form(self): … … 94 100 response = self.client.post('/test_client/form_view/', post_data) 95 101 self.assertEqual(response.status_code, 200) 96 self.assert Equal(response.template.name, "Valid POST Template")102 self.assertTemplateUsed(response, "Valid POST Template") 97 103 98 104 def test_incomplete_data_form(self): … … 103 109 } 104 110 response = self.client.post('/test_client/form_view/', post_data) 105 self.assertEqual(response.status_code, 200) 106 self.assertEqual(response.template.name, "Invalid POST Template") 111 self.assertContains(response, 'This field is required.', 3) 112 self.assertEqual(response.status_code, 200) 113 self.assertTemplateUsed(response, "Invalid POST Template") 114 115 self.assertFormError(response, 'form', 'email', 'This field is required.') 116 self.assertFormError(response, 'form', 'single', 'This field is required.') 117 self.assertFormError(response, 'form', 'multi', 'This field is required.') 107 118 108 119 def test_form_error(self): … … 117 128 response = self.client.post('/test_client/form_view/', post_data) 118 129 self.assertEqual(response.status_code, 200) 119 self.assertEqual(response.template.name, "Invalid POST Template") 130 self.assertTemplateUsed(response, "Invalid POST Template") 131 132 self.assertFormError(response, 'form', 'email', 'Enter a valid e-mail address.') 133 134 def test_valid_form_with_template(self): 135 "POST valid data to a form using multiple templates" 136 post_data = { 137 'text': 'Hello World', 138 'email': 'foo@example.com', 139 'value': 37, 140 'single': 'b', 141 'multi': ('b','c','e') 142 } 143 response = self.client.post('/test_client/form_view_with_template/', post_data) 144 self.assertContains(response, 'POST data OK') 145 self.assertTemplateUsed(response, "form_view.html") 146 self.assertTemplateUsed(response, 'base.html') 147 self.assertTemplateNotUsed(response, "Valid POST Template") 148 149 def test_incomplete_data_form_with_template(self): 150 "POST incomplete data to a form using multiple templates" 151 post_data = { 152 'text': 'Hello World', 153 'value': 37 154 } 155 response = self.client.post('/test_client/form_view_with_template/', post_data) 156 self.assertContains(response, 'POST data has errors') 157 self.assertTemplateUsed(response, 'form_view.html') 158 self.assertTemplateUsed(response, 'base.html') 159 self.assertTemplateNotUsed(response, "Invalid POST Template") 160 161 self.assertFormError(response, 'form', 'email', 'This field is required.') 162 self.assertFormError(response, 'form', 'single', 'This field is required.') 163 self.assertFormError(response, 'form', 'multi', 'This field is required.') 164 165 def test_form_error_with_template(self): 166 "POST erroneous data to a form using multiple templates" 167 post_data = { 168 'text': 'Hello World', 169 'email': 'not an email address', 170 'value': 37, 171 'single': 'b', 172 'multi': ('b','c','e') 173 } 174 response = self.client.post('/test_client/form_view_with_template/', post_data) 175 self.assertContains(response, 'POST data has errors') 176 self.assertTemplateUsed(response, "form_view.html") 177 self.assertTemplateUsed(response, 'base.html') 178 self.assertTemplateNotUsed(response, "Invalid POST Template") 179 180 self.assertFormError(response, 'form', 'email', 'Enter a valid e-mail address.') 120 181 121 182 def test_unknown_page(self): … … 131 192 # Get the page without logging in. Should result in 302. 132 193 response = self.client.get('/test_client/login_protected_view/') 133 self.assertEqual(response.status_code, 302) 134 194 self.assertRedirects(response, '/accounts/login/') 195 196 # Log in 197 self.client.login(username='testclient', password='password') 198 135 199 # Request a page that requires a login 136 response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password') 137 self.failUnless(response) 200 response = self.client.get('/test_client/login_protected_view/') 138 201 self.assertEqual(response.status_code, 200) 139 202 self.assertEqual(response.context['user'].username, 'testclient') 140 self.assertEqual(response.template.name, 'Login Template')141 203 142 204 def test_view_with_bad_login(self): 143 205 "Request a page that is protected with @login, but use bad credentials" 144 206 145 response = self.client.login('/test_client/login_protected_view/', 'otheruser','nopassword')146 self.failIf( response)207
