Commits

Anonymous committed 5be717d

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

Comments (0)

Files changed (8)

django/contrib/auth/tests/views.py

 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):
     """
 
     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])
         # 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()
         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
         # 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()
         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()
                                            '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'):

django/test/__init__.py

 
 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

django/test/decorators.py

+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

django/test/simple.py

-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
         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)
+

docs/topics/testing.txt

 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

tests/regressiontests/admin_views/urls.py

 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),

tests/regressiontests/test_decorators/tests.py

+"""
+>>> 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
+
+"""
     # 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')),