Author: kkubasik
Date: 2009-07-01 09:24:25 -0500 (Wed, 01 Jul 2009)
New Revision: 11142
Added:
django/branches/soc2009/test-improvements/django/test/decorators.py
django/branches/soc2009/test-improvements/tests/regressiontests/test_decorators/
django/branches/soc2009/test-improvements/tests/regressiontests/test_decorators/tests.py
Modified:
django/branches/soc2009/test-improvements/
django/branches/soc2009/test-improvements/django/contrib/auth/tests/views.py
django/branches/soc2009/test-improvements/django/test/__init__.py
django/branches/soc2009/test-improvements/django/test/simple.py
django/branches/soc2009/test-improvements/docs/topics/testing.txt
django/branches/soc2009/test-improvements/tests/regressiontests/admin_views/urls.py
django/branches/soc2009/test-improvements/tests/urls.py
Log:
[gsoc2009-testing] Added support for skipping tests that cannot pass. Add auth
to regression suite urls.py so reverse() works.
Property changes on: django/branches/soc2009/test-improvements
___________________________________________________________________
Name: svk:merge
- 23ef3597-c209-482b-90c0-ea6045f15f7f:/local/django-gsoc:10962
23ef3597-c209-482b-90c0-ea6045f15f7f:/local/django/trunk:10927
bcc190cf-cafb-0310-a4f2-bffc1f526a37:/django/trunk:1054
+ 23ef3597-c209-482b-90c0-ea6045f15f7f:/local/django-gsoc:10969
23ef3597-c209-482b-90c0-ea6045f15f7f:/local/django/trunk:10927
bcc190cf-cafb-0310-a4f2-bffc1f526a37:/django/trunk:1054
Modified:
django/branches/soc2009/test-improvements/django/contrib/auth/tests/views.py
===================================================================
---
django/branches/soc2009/test-improvements/django/contrib/auth/tests/views.py
2009-07-01 10:40:09 UTC (rev 11141)
+++
django/branches/soc2009/test-improvements/django/contrib/auth/tests/views.py
2009-07-01 14:24:25 UTC (rev 11142)
@@ -9,6 +9,7 @@
from django.test import TestCase
from django.core import mail
from django.core.urlresolvers import reverse
+from django.test.decorators import views_required
class AuthViewsTestCase(TestCase):
"""
@@ -49,22 +50,26 @@
def test_email_not_found(self):
"Error is raised if the provided email address isn't currently
registered"
- response = self.client.get('/password_reset/')
+ response =
self.client.get(reverse('django.contrib.auth.views.password_reset'))
self.assertEquals(response.status_code, 200)
- response = self.client.post('/password_reset/', {'email':
'[email protected]'})
+ response =
self.client.post(reverse('django.contrib.auth.views.password_reset'), {'email':
'[email protected]'})
self.assertContains(response, "That e-mail address doesn't have an
associated user account")
self.assertEquals(len(mail.outbox), 0)
-
+
+ test_email_not_found =
views_required(required_views=['django.contrib.auth.views.password_reset'])(test_email_not_found)
+
def test_email_found(self):
"Email is sent if a valid email address is provided for password reset"
- response = self.client.post('/password_reset/', {'email':
'[email protected]'})
+ response =
self.client.post(reverse('django.contrib.auth.views.password_reset'), {'email':
'[email protected]'})
self.assertEquals(response.status_code, 302)
self.assertEquals(len(mail.outbox), 1)
self.assert_("http://" in mail.outbox[0].body)
+
+ test_email_found =
views_required(required_views=['django.contrib.auth.views.password_reset'])(test_email_found)
def _test_confirm_start(self):
# Start by creating the email
- response = self.client.post('/password_reset/', {'email':
'[email protected]'})
+ response =
self.client.post(reverse('django.contrib.auth.views.password_reset'), {'email':
'[email protected]'})
self.assertEquals(response.status_code, 302)
self.assertEquals(len(mail.outbox), 1)
return self._read_signup_email(mail.outbox[0])
@@ -80,7 +85,9 @@
# redirect to a 'complete' page:
self.assertEquals(response.status_code, 200)
self.assert_("Please enter your new password" in response.content)
+ test_confirm_valid =
views_required(required_views=['django.contrib.auth.views.password_reset'])(test_confirm_valid)
+
def test_confirm_invalid(self):
url, path = self._test_confirm_start()
# Let's munge the token in the path, but keep the same length,
@@ -90,6 +97,7 @@
response = self.client.get(path)
self.assertEquals(response.status_code, 200)
self.assert_("The password reset link was invalid" in response.content)
+ test_confirm_invalid =
views_required(required_views=['django.contrib.auth.views.password_reset'])(test_confirm_invalid)
def test_confirm_invalid_post(self):
# Same as test_confirm_invalid, but trying
@@ -102,6 +110,7 @@
# Check the password has not been changed
u = User.objects.get(email='[email protected]')
self.assert_(not u.check_password("anewpassword"))
+ test_confirm_invalid_post =
views_required(required_views=['django.contrib.auth.views.password_reset'])(test_confirm_invalid_post)
def test_confirm_complete(self):
url, path = self._test_confirm_start()
@@ -117,6 +126,7 @@
response = self.client.get(path)
self.assertEquals(response.status_code, 200)
self.assert_("The password reset link was invalid" in response.content)
+ test_confirm_complete =
views_required(required_views=['django.contrib.auth.views.password_reset'])(test_confirm_complete)
def test_confirm_different_passwords(self):
url, path = self._test_confirm_start()
@@ -124,7 +134,8 @@
'new_password2':' x'})
self.assertEquals(response.status_code, 200)
self.assert_("The two password fields didn't match" in
response.content)
-
+
+ test_confirm_different_passwords =
views_required(required_views=['django.contrib.auth.views.password_reset'])(test_confirm_different_passwords)
class ChangePasswordTest(AuthViewsTestCase):
def login(self, password='password'):
Modified: django/branches/soc2009/test-improvements/django/test/__init__.py
===================================================================
--- django/branches/soc2009/test-improvements/django/test/__init__.py
2009-07-01 10:40:09 UTC (rev 11141)
+++ django/branches/soc2009/test-improvements/django/test/__init__.py
2009-07-01 14:24:25 UTC (rev 11142)
@@ -4,3 +4,10 @@
from django.test.client import Client
from django.test.testcases import TestCase, TransactionTestCase
+
+class SkippedTest(Exception):
+ def __init__(self, reason):
+ self.reason = reason
+
+ def __str__(self):
+ return self.reason
\ No newline at end of file
Added: django/branches/soc2009/test-improvements/django/test/decorators.py
===================================================================
--- django/branches/soc2009/test-improvements/django/test/decorators.py
(rev 0)
+++ django/branches/soc2009/test-improvements/django/test/decorators.py
2009-07-01 14:24:25 UTC (rev 11142)
@@ -0,0 +1,44 @@
+from django.core import urlresolvers
+from django.test import SkippedTest
+
+def views_required(required_views=[]):
+ def urls_found():
+ try:
+ for view in required_views:
+ urlresolvers.reverse(view)
+ return True
+ except urlresolvers.NoReverseMatch:
+ return False
+ reason = 'Required view%s for this test not found: %s' % \
+ (len(required_views) > 1 and 's' or '', ', '.join(required_views))
+ return conditional_skip(urls_found, reason=reason)
+
+def modules_required(required_modules=[]):
+ def modules_found():
+ try:
+ for module in required_modules:
+ __import__(module)
+ return True
+ except ImportError:
+ return False
+ reason = 'Required module%s for this test not found: %s' % \
+ (len(required_modules) > 1 and 's' or '', ',
'.join(required_modules))
+ return conditional_skip(modules_found, reason=reason)
+
+def skip_specific_database(database_engine):
+ def database_check():
+ from django.conf import settings
+ return database_engine == settings.DATABASE_ENGINE
+ reason = 'Test not run for database engine %s.' % database_engine
+ return conditional_skip(database_check, reason=reason)
+
+def conditional_skip(required_condition, reason=''):
+ if required_condition():
+ return lambda x: x
+ else:
+ return skip_test(reason)
+
+def skip_test(reason=''):
+ def _skip(x):
+ raise SkippedTest(reason=reason)
+ return lambda x: _skip
Modified: django/branches/soc2009/test-improvements/django/test/simple.py
===================================================================
--- django/branches/soc2009/test-improvements/django/test/simple.py
2009-07-01 10:40:09 UTC (rev 11141)
+++ django/branches/soc2009/test-improvements/django/test/simple.py
2009-07-01 14:24:25 UTC (rev 11142)
@@ -1,4 +1,4 @@
-import unittest
+import sys, time, traceback, unittest
from django.conf import settings
from django.db.models import get_app, get_apps
from django.test import _doctest as doctest
@@ -202,9 +202,95 @@
old_name = settings.DATABASE_NAME
from django.db import connection
connection.creation.create_test_db(verbosity, autoclobber=not
interactive)
- result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
+ result = SkipTestRunner(verbosity=verbosity).run(suite)
connection.creation.destroy_test_db(old_name, verbosity)
teardown_test_environment()
return len(result.failures) + len(result.errors)
+
+
+class SkipTestRunner:
+ """
+ A test runner class that adds a Skipped category in the output layer.
+
+ Modeled after unittest.TextTestRunner.
+
+ Similarly to unittest.TextTestRunner, prints summary of the results at the
end.
+ (Including a count of skipped tests.)
+ """
+
+ def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1):
+ self.stream = unittest._WritelnDecorator(stream)
+ self.descriptions = descriptions
+ self.verbosity = verbosity
+ self.result = _SkipTestResult(self.stream, descriptions, verbosity)
+
+ def run(self, test):
+ "Run the given test case or test suite."
+ startTime = time.time()
+ test.run(self.result)
+ stopTime = time.time()
+ timeTaken = stopTime - startTime
+
+ self.result.printErrors()
+ self.stream.writeln(self.result.separator2)
+ run = self.result.testsRun
+ self.stream.writeln('Ran %d test%s in %.3fs' %
+ (run, run != 1 and 's' or '', timeTaken))
+ self.stream.writeln()
+ if not self.result.wasSuccessful():
+ self.stream.write('FAILED (')
+ failed, errored, skipped = map(len, (self.result.failures,
self.result.errors, self.result.skipped))
+ if failed:
+ self.stream.write('failures=%d' % failed)
+ if errored:
+ if failed: self.stream.write(', ')
+ self.stream.write('errors=%d' % errored)
+ if skipped:
+ if errored or failed: self.stream.write(', ')
+ self.stream.write('skipped=%d' % skipped)
+ self.stream.writeln(')')
+ else:
+ self.stream.writeln('OK')
+ return self.result
+
+class _SkipTestResult(unittest._TextTestResult):
+ """
+ A test result class that adds a Skipped category in the output layer.
+
+ Modeled after unittest._TextTestResult.
+
+ Similarly to unittest._TextTestResult, prints out the names of tests as
they are
+ run and errors as they occur.
+ """
+
+ def __init__(self, stream, descriptions, verbosity):
+ unittest._TextTestResult.__init__(self, stream, descriptions,
verbosity)
+ self.skipped = []
+
+ def addError(self, test, err):
+ # Determine if this is a skipped test
+ tracebacks = traceback.extract_tb(err[2])
+ if tracebacks[-1][-1].startswith('raise SkippedTest'):
+ self.skipped.append((test, self._exc_info_to_string(err, test)))
+ if self.showAll:
+ self.stream.writeln('SKIPPED')
+ elif self.dots:
+ self.stream.write('S')
+ self.stream.flush()
+ else:
+ unittest.TestResult.addError(self, test, err)
+ if self.showAll:
+ self.stream.writeln('ERROR')
+ elif self.dots:
+ self.stream.write('E')
+ self.stream.flush()
+
+ def printErrors(self):
+ if self.dots or self.showAll:
+ self.stream.writeln()
+ self.printErrorList('SKIPPED', self.skipped)
+ self.printErrorList('ERROR', self.errors)
+ self.printErrorList('FAIL', self.failures)
+
Modified: django/branches/soc2009/test-improvements/docs/topics/testing.txt
===================================================================
--- django/branches/soc2009/test-improvements/docs/topics/testing.txt
2009-07-01 10:40:09 UTC (rev 11141)
+++ django/branches/soc2009/test-improvements/docs/topics/testing.txt
2009-07-01 14:24:25 UTC (rev 11142)
@@ -1045,6 +1045,58 @@
This test case will load the contents of ``myapp.test_models`` and add
any subclass of ``django.db.models.Model`` to ``myapp.models``.
+Skipping tests bound to fail
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded: 1.1
+
+Occasionally it's helpful to specify tests that are skipped under certain
+circumstances. To accomplish this, the Django test framework offers decorators
+that you can apply to your test methods for them to be conditionally skipped.
+
+You can supply your own condition function as follows::
+
+ from django.tests.decorators import *
+
+ class TestUnderCondition(TestCase):
+
+ def _my_condition():
+ # Condition returning True if test should be run and False if it
+ # should be skipped.
+
+ @conditional_skip(_my_condition, reason='This test should be skipped
sometimes')
+ def testOnlyOnTuesday(self):
+ # Test to run if _my_condition evaluates to True
+
+In addition, the Django test framework supplies a handful of skip conditions
that
+handle commonly used conditions for skipping tests.
+
+``views_required(required_views=[])``
+ Does a ``urlresolver.Reverse`` on the required views supplied. Runs test
only if
+ all views in ``required_views`` are in use.
+
+``modules_required(required_modules=[])``
+ Runs tests only if all modules in ``required_modules`` can be imported.
+
+``skip_specific_database(database_engine)``
+ Skips test if ``settings.DATABASE_ENGINE`` is equal to database_engine.
+
+If a test is skipped, it is added to a skipped category in the test runner and
+the test results are reported as such::
+
+ ======================================================================
+ SKIPPED: test_email_found
(django.contrib.auth.tests.basic.PasswordResetTest)
+ ----------------------------------------------------------------------
+ Traceback (most recent call last):
+ File "/Users/dnaquin/Dropbox/Sandbox/django/django/test/decorators.py",
line 43, in _skip
+ raise SkippedTest(reason=reason)
+ SkippedTest: Required view for this test not found:
django.contrib.auth.views.password_reset
+
+ ----------------------------------------------------------------------
+ Ran 408 tests in 339.663s
+
+ FAILED (failures=1, skipped=2)
+
.. _emptying-test-outbox:
Emptying the test outbox
Modified:
django/branches/soc2009/test-improvements/tests/regressiontests/admin_views/urls.py
===================================================================
---
django/branches/soc2009/test-improvements/tests/regressiontests/admin_views/urls.py
2009-07-01 10:40:09 UTC (rev 11141)
+++
django/branches/soc2009/test-improvements/tests/regressiontests/admin_views/urls.py
2009-07-01 14:24:25 UTC (rev 11142)
@@ -2,7 +2,7 @@
from django.contrib import admin
import views
import customadmin
-admin.autodiscover()
+#admin.autodiscover()
urlpatterns = patterns('',
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
(r'^admin/secure-view/$', views.secure_view),
Added:
django/branches/soc2009/test-improvements/tests/regressiontests/test_decorators/tests.py
===================================================================
---
django/branches/soc2009/test-improvements/tests/regressiontests/test_decorators/tests.py
(rev 0)
+++
django/branches/soc2009/test-improvements/tests/regressiontests/test_decorators/tests.py
2009-07-01 14:24:25 UTC (rev 11142)
@@ -0,0 +1,23 @@
+"""
+>>> from django.test import SkippedTest
+>>> from django.test.decorators import *
+
+>>> skip_test()(None)(None)
+Traceback (most recent call last):
+ ...
+SkippedTest
+
+>>> skip_test(reason='testing')(None)(None)
+Traceback (most recent call last):
+ ...
+SkippedTest: testing
+
+>>> conditional_skip(lambda: False)(None)(None)
+Traceback (most recent call last):
+ ...
+SkippedTest
+
+>>> conditional_skip(lambda: True)(lambda: True)()
+True
+
+"""
Modified: django/branches/soc2009/test-improvements/tests/urls.py
===================================================================
--- django/branches/soc2009/test-improvements/tests/urls.py 2009-07-01
10:40:09 UTC (rev 11141)
+++ django/branches/soc2009/test-improvements/tests/urls.py 2009-07-01
14:24:25 UTC (rev 11142)
@@ -11,6 +11,7 @@
# Always provide the auth system login and logout views
(r'^accounts/login/$', 'django.contrib.auth.views.login',
{'template_name': 'login.html'}),
(r'^accounts/logout/$', 'django.contrib.auth.views.logout'),
+ (r'^accounts2/', include('django.contrib.auth.urls')),
# test urlconf for {% url %} template tag
(r'^url_tag/', include('regressiontests.templates.urls')),
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---