1
0
mirror of https://github.com/django/django.git synced 2025-07-05 10:19:20 +00:00

[gsoc2009-testing] Added support for skipping tests that cannot pass. Add auth to regression suite urls.py so reverse() works.

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/test-improvements@11142 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Kevin Kubasik 2009-07-01 14:24:25 +00:00
parent 8cd4df145f
commit f2be59849c
8 changed files with 233 additions and 9 deletions

View File

@ -9,6 +9,7 @@ from django.contrib.auth.models import User
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 @@ class PasswordResetTest(AuthViewsTestCase):
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': 'not_a_real_email@email.com'})
response = self.client.post(reverse('django.contrib.auth.views.password_reset'), {'email': 'not_a_real_email@email.com'})
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': 'staffmember@example.com'})
response = self.client.post(reverse('django.contrib.auth.views.password_reset'), {'email': 'staffmember@example.com'})
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': 'staffmember@example.com'})
response = self.client.post(reverse('django.contrib.auth.views.password_reset'), {'email': 'staffmember@example.com'})
self.assertEquals(response.status_code, 302)
self.assertEquals(len(mail.outbox), 1)
return self._read_signup_email(mail.outbox[0])
@ -80,6 +85,8 @@ class PasswordResetTest(AuthViewsTestCase):
# 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()
@ -90,6 +97,7 @@ class PasswordResetTest(AuthViewsTestCase):
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 @@ class PasswordResetTest(AuthViewsTestCase):
# Check the password has not been changed
u = User.objects.get(email='staffmember@example.com')
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 @@ class PasswordResetTest(AuthViewsTestCase):
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 @@ class PasswordResetTest(AuthViewsTestCase):
'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'):

View File

@ -4,3 +4,10 @@ Django Unit Test and Doctest framework.
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

44
django/test/decorators.py Normal file
View File

@ -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

View File

@ -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 @@ class DefaultTestRunner(object):
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)

View File

@ -1045,6 +1045,58 @@ For example::
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

View File

@ -2,7 +2,7 @@ from django.conf.urls.defaults import *
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),

View File

@ -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
"""

View File

@ -11,6 +11,7 @@ urlpatterns = patterns('',
# 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')),