Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Adding the Django Discovery TestRunner #17365 #319

Closed
wants to merge 23 commits into from

6 participants

@myusuf3

This pull request adds the unittest2 test discovery to Django while specifying location for look up in settings.py

That being said, I have only updated the 1.5.txt release until the code is reviewed. Once that is complete I will update the testing documentation and what other documentation that needs to be updated as per discussion had here.

Also let me know if I can improve the testing. I think I have tested the discovery test runner well but if it can be improved to cover edge cases let me know.

Many thanks to @freakboy3742 for his guidance.

https://code.djangoproject.com/ticket/17365 is the attached ticket

@mjtamlyn
Collaborator

I'm guessing there's a ticket this is associated to?

Also is the intention to replace the old test runner with the DiscoverRunner? Not that I want it, I tend to use DiscoverRunner anyway, but some people may want the old behaviour

@jezdez
Owner

FWIW, given this is a copy of the django-discover-runner code and the feature in unitttest2 is called "discover", I'd prefer to use the name "DiscoverRunner" instead of "DiscoveryRunner".

@jezdez
Owner

Also, the defaultTestLoader must be imported like in the change jezdez/django-discover-runner@0fdc131 since we wrap an installed unittest2 in the django.utils.unittest module and importing from django.utils.loader is the one included in Django. Not doing so prevents an isinstance check in unittest2 from succeeding, leading to the symptoms as mentioned in jezdez/django-discover-runner#2.

@jezdez
Owner

As to whether we should support the old test runner, I strongly suggest to deprecate our own test runner, making the DiscoverRunner the default in Django>=1.5 projects, switching to the DiscoverRunner by default in 1.6, and remove the old runner in 1.7.

@myusuf3

@mjtamlyn I have updated the pull request to refer to the ticket.

@jezdez Those changes can be made. That is the planned depreciation schedule in place for this.

@jacobian
Collaborator

FTR, I'm +1 on @jezdez's proposal.

@jezdez
Owner

Woot!

django/conf/global_settings.py
@@ -293,7 +293,7 @@
# Maximum size, in bytes, of a request before it will be streamed to the
# file system instead of into memory.
-FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB
+FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB
@akumria
akumria added a note

Is it necessary to have the PEP8 fixes in this pull request? I'm not sure what the recommendation is for the Django repository, but in my own repositories I ask for either a PEP8 fix before the code changes, or one just after (to separate out changes which should have no functional impact).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
django/conf/global_settings.py
@@ -346,11 +346,11 @@
# http://docs.python.org/library/datetime.html#strftime-behavior
# * Note that these format strings are different from the ones to display dates
DATE_INPUT_FORMATS = (
- '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
- '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
- '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
- '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
- '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
+ '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
+ '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
+ '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
+ '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
+ '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
@akumria
akumria added a note

likewise ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
django/test/simple.py
@@ -110,7 +110,7 @@ def build_test(label):
try:
if issubclass(TestClass, (unittest.TestCase, real_unittest.TestCase)):
- if len(parts) == 2: # label is app.TestClass
+ if len(parts) == 2: # label is app.TestClass
@akumria
akumria added a note

as before ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@akumria akumria commented on the diff
django/utils/unittest/loader.py
@@ -24,6 +24,7 @@ def _CmpToKey(mycmp):
class K(object):
def __init__(self, obj):
self.obj = obj
+
@akumria
akumria added a note

again with this file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@myusuf3

so documentation here all that needs to be done? I would like to get this in before the freeze if possible. @jezdez ?

@jezdez
Owner

Yeah, the code looks good to me, doc updates could easily land after the fact, IMO.

@myusuf3

@jezdez so @carljm said that is probably wont get into 1.5 it needs more review and adds too many setting to the setting.py file.

So I guess that's it for now.

@carljm
Owner

Sorry I haven't done a fuller comment on this sooner. I worked on this some at the DjangoCon sprints and talked it over quite a bit with Russ, Jacob, and others. I have some work locally but haven't pushed it yet since it's not yet in a fully working state. I'll try to clean that work up and get it pushed soon. Specifically the things that IMO are not ready yet with this patch:

  1. @jacobian wanted to avoid adding any new settings with this; things like the test discovery pattern should be set instead as command-line arguments.

  2. If we are deprecating the old runner, it's no good to have the new runner inherit from the deprecated runner; that's just postponing a bunch of work to whoever does the final deprecation removal, deprecation removals should not require big code reorganizations. Instead the new runner needs to be standalone with all required functionality, and the old runner should inherit from the new runner, so when it is removed nothing else is affected.

  3. Docs - I don't think we should commit things without adequate docs and then clean it up later. Writing the docs helps us figure out if we've made mistakes in the API. Docs are a first-class citizen.

  4. The current code does a kind of funny thing where it tries to intelligently decide whether to use discovery or loadTestsFromModule depending what you pass on the command line and what it finds there. I did this because it was functional and what I wanted for an external project, but I'm not sure this approach belongs in Django - this is just going right back down the path of inventing our own unique discovery process. I think we should maybe stick closer to the native behavior of unittest, and discuss with voidspace possible improvements to that behavior. This requires more discussion.

And that's really the key - while I'd like to see this change in Django, it's a significant developer-facing change and I think it's worth taking the time to make sure we do it right. And that means not committing something quick before feature freeze just to get it in, it means plenty of time for consideration and review on django-developers. Unfortunately I didn't have time this summer to drive that, but I'm hoping I (or someone else?) will in the 1.6 cycle.

@carljm
Owner

I meant to also say - many thanks @myusuf3 for getting things rolling with this pull request. I'll leave a note here when I get my WIP pushed, in case you or anyone else want to carry it forward before I get back to it.

@jezdez
Owner

@carljm Thanks for the summary and update, I agree on all counts and updated the ticket.

@myusuf3

@carljm @jacobian @jezdez so what can we do to get the ball rolling here? I would like to contribute to make sure this gets in;

I am just trying to understand the process here. I found the ticket read the discussion spoke with @freakboy3742 with respect on how to go about solving it and what required tests. I understand having different ideas on how to go about things and having a discussion. I am just trying to understand what the process is.

@carljm
Owner

Hi @myusuf3 - my apologies, I screwed up the process by saying here that I'd clean up and push my work-in-progress, and then never actually doing so. I think I outlined in a comment above what I think needs to be done in order for this to be ready to go in; I still haven't found time to finish or clean up my work-in-progress from the DjangoCon sprints, but I've now pushed it, (as-is and not-yet-working) to https://github.com/carljm/django/tree/ticket-17365

If you'd like to work on this, you can start where I left off if that's helpful. If you have questions about the direction I was heading with that branch, I'm happy to talk it over more here or on IRC. I think there are still some pretty significant design decisions to be made in terms of how the test discovery actually works (item 4 in my comment above; how much, if any, custom extra behavior we add on top of unittest2's default discovery behavior), so I don't think this is just a matter of making some minor code tweaks and getting it merged, it's probably going to merit a proposal and discussion on django-developers to gather feedback on the right behavior.

Again, sorry for throwing a wrench in the works by not following through after my last comments. The process from here on out, as far as I'm concerned, is to address points 1-3 above, make a proposal to django-developers regarding the precise discovery behavior (point 4 above), reach agreement on that and implement it, get review of the patch/pull request (I'm happy to do review at that stage), and then hopefully get it merged!

@myusuf3 myusuf3 closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 8, 2012
  1. @myusuf3

    updating for lint fixes

    myusuf3 authored
  2. @myusuf3
  3. @myusuf3
  4. @myusuf3
  5. @myusuf3
  6. @myusuf3

    adding linter fixes

    myusuf3 authored
  7. @myusuf3
Commits on Aug 11, 2012
  1. @myusuf3
Commits on Aug 12, 2012
  1. @myusuf3
Commits on Aug 15, 2012
  1. @myusuf3
  2. @myusuf3
  3. @myusuf3
  4. @myusuf3

    removing tests dir

    myusuf3 authored
Commits on Aug 16, 2012
  1. @myusuf3
Commits on Sep 1, 2012
  1. @myusuf3
Commits on Sep 2, 2012
  1. @myusuf3

    adding test running title

    myusuf3 authored
Commits on Sep 3, 2012
  1. @myusuf3
Commits on Sep 4, 2012
  1. @myusuf3

    updating release docs.

    myusuf3 authored
Commits on Sep 8, 2012
  1. @myusuf3
  2. @myusuf3
  3. @carljm
  4. @carljm
Commits on Sep 19, 2012
  1. @carljm

    WIP

    carljm authored
This page is out of date. Refresh to see the latest.
View
4 django/conf/project_template/project_name/settings.py
@@ -122,6 +122,10 @@
# 'django.contrib.admindocs',
)
+
+TEST_RUNNER = 'django.test.runner.DiscoverRunner'
+
+
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
View
231 django/test/runner.py
@@ -0,0 +1,231 @@
+import os
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.test import TestCase
+from django.test.utils import setup_test_environment, teardown_test_environment
+from django.utils.importlib import import_module
+from django.utils import unittest
+from django.utils.unittest.loader import defaultTestLoader
+
+
+class DjangoTestDiscoverRunner(object):
+ """A Django test runner that uses unittest2 test discovery."""
+ def __init__(self, verbosity=1, interactive=True, failfast=True, **kwargs):
+ self.verbosity = verbosity
+ self.interactive = interactive
+ self.failfast = failfast
+
+ def setup_test_environment(self, **kwargs):
+ setup_test_environment()
+ settings.DEBUG = False
+ unittest.installHandler()
+
+ def build_suite(self, test_labels, extra_tests=None, **kwargs):
+ suite = None
+ root = settings.TEST_DISCOVER_ROOT
+ pattern = settings.TEST_DISCOVER_PATTERN
+ top_level = settings.TEST_DISCOVER_TOP_LEVEL
+
+ if test_labels:
+ suite = defaultTestLoader.loadTestsFromNames(test_labels)
+ # if single named module has no tests, do discovery within the
+ # module itself
+ if not suite.countTestCases() and len(test_labels) == 1:
+ suite = None
+ root = import_module(test_labels[0]).__path__[0]
+ top_level = os.path.dirname(root)
+
+ if suite is None:
+ suite = defaultTestLoader.discover(root,
+ pattern=pattern,
+ top_level_dir=top_level,
+ )
+
+ if extra_tests:
+ for test in extra_tests:
+ suite.addTest(test)
+
+ return reorder_suite(suite, (TestCase,))
+
+ def setup_databases(self, **kwargs):
+ from django.db import connections, DEFAULT_DB_ALIAS
+
+ # First pass -- work out which databases actually need to be created,
+ # and which ones are test mirrors or duplicate entries in DATABASES
+ mirrored_aliases = {}
+ test_databases = {}
+ dependencies = {}
+ for alias in connections:
+ connection = connections[alias]
+ if connection.settings_dict['TEST_MIRROR']:
+ # If the database is marked as a test mirror, save
+ # the alias.
+ mirrored_aliases[alias] = (
+ connection.settings_dict['TEST_MIRROR'])
+ else:
+ # Store a tuple with DB parameters that uniquely identify it.
+ # If we have two aliases with the same values for that tuple,
+ # we only need to create the test database once.
+ item = test_databases.setdefault(
+ connection.creation.test_db_signature(),
+ (connection.settings_dict['NAME'], set())
+ )
+ item[1].add(alias)
+
+ if 'TEST_DEPENDENCIES' in connection.settings_dict:
+ dependencies[alias] = (
+ connection.settings_dict['TEST_DEPENDENCIES'])
+ else:
+ if alias != DEFAULT_DB_ALIAS:
+ dependencies[alias] = connection.settings_dict.get(
+ 'TEST_DEPENDENCIES', [DEFAULT_DB_ALIAS])
+
+ # Second pass -- actually create the databases.
+ old_names = []
+ mirrors = []
+
+ for signature, (db_name, aliases) in dependency_ordered(
+ test_databases.items(), dependencies):
+ test_db_name = None
+ # Actually create the database for the first connection
+
+ for alias in aliases:
+ connection = connections[alias]
+ old_names.append((connection, db_name, True))
+ if test_db_name is None:
+ test_db_name = connection.creation.create_test_db(
+ self.verbosity, autoclobber=not self.interactive)
+ else:
+ connection.settings_dict['NAME'] = test_db_name
+
+ for alias, mirror_alias in mirrored_aliases.items():
+ mirrors.append((alias, connections[alias].settings_dict['NAME']))
+ connections[alias].settings_dict['NAME'] = (
+ connections[mirror_alias].settings_dict['NAME'])
+
+ return old_names, mirrors
+
+ def run_suite(self, suite, **kwargs):
+ return unittest.TextTestRunner(
+ verbosity=self.verbosity, failfast=self.failfast).run(suite)
+
+ def teardown_databases(self, old_config, **kwargs):
+ """
+ Destroys all the non-mirror databases.
+ """
+ old_names, mirrors = old_config
+ for connection, old_name, destroy in old_names:
+ if destroy:
+ connection.creation.destroy_test_db(old_name, self.verbosity)
+
+ def teardown_test_environment(self, **kwargs):
+ unittest.removeHandler()
+ teardown_test_environment()
+
+ def suite_result(self, suite, result, **kwargs):
+ return len(result.failures) + len(result.errors)
+
+ def run_tests(self, test_labels, discovery_root, extra_tests=None, **kwargs):
+ """
+ Run the unit tests for all the test labels in the provided list.
+
+ Test labels should be dotted Python paths to test modules, test
+ classes, or test methods.
+
+ If no test labels are provided, tests will be discovered under the
+ filesystem path ``discovery_root``.
+
+ A list of 'extra' tests may also be provided; these tests
+ will be added to the test suite.
+
+ Returns the number of tests that failed.
+ """
+ self.setup_test_environment()
+ suite = self.build_suite(test_labels, extra_tests)
+ old_config = self.setup_databases()
+ result = self.run_suite(suite)
+ self.teardown_databases(old_config)
+ self.teardown_test_environment()
+ return self.suite_result(suite, result)
+
+
+def dependency_ordered(test_databases, dependencies):
+ """
+ Reorder test_databases into an order that honors the dependencies
+ described in TEST_DEPENDENCIES.
+ """
+ ordered_test_databases = []
+ resolved_databases = set()
+
+ # Maps db signature to dependencies of all it's aliases
+ dependencies_map = {}
+
+ # sanity check - no DB can depend on it's own alias
+ for sig, (_, aliases) in test_databases:
+ all_deps = set()
+ for alias in aliases:
+ all_deps.update(dependencies.get(alias, []))
+ if not all_deps.isdisjoint(aliases):
+ raise ImproperlyConfigured(
+ "Circular dependency: databases %r depend on each other, "
+ "but are aliases." % aliases)
+ dependencies_map[sig] = all_deps
+
+ while test_databases:
+ changed = False
+ deferred = []
+
+ # Try to find a DB that has all it's dependencies met
+ for signature, (db_name, aliases) in test_databases:
+ if dependencies_map[signature].issubset(resolved_databases):
+ resolved_databases.update(aliases)
+ ordered_test_databases.append((signature, (db_name, aliases)))
+ changed = True
+ else:
+ deferred.append((signature, (db_name, aliases)))
+
+ if not changed:
+ raise ImproperlyConfigured(
+ "Circular dependency in TEST_DEPENDENCIES")
+ test_databases = deferred
+ return ordered_test_databases
+
+
+def reorder_suite(suite, classes):
+ """
+ Reorders a test suite by test type.
+
+ `classes` is a sequence of types
+
+ All tests of type classes[0] are placed first, then tests of type
+ classes[1], etc. Tests with no match in classes are placed last.
+ """
+ class_count = len(classes)
+ bins = [unittest.TestSuite() for i in range(class_count+1)]
+ partition_suite(suite, classes, bins)
+ for i in range(class_count):
+ bins[0].addTests(bins[i+1])
+ return bins[0]
+
+
+def partition_suite(suite, classes, bins):
+ """
+ Partitions a test suite by test type.
+
+ classes is a sequence of types
+ bins is a sequence of TestSuites, one more than classes
+
+ Tests of type classes[i] are added to bins[i],
+ tests with no match found in classes are place in bins[-1]
+ """
+ for test in suite:
+ if isinstance(test, unittest.TestSuite):
+ partition_suite(test, classes, bins)
+ else:
+ for i in range(len(classes)):
+ if isinstance(test, classes[i]):
+ bins[i].addTest(test)
+ break
+ else:
+ bins[-1].addTest(test)
View
218 django/test/simple.py
@@ -1,10 +1,17 @@
+"""
+This module is pending deprecation as of Django 1.5 and will be removed in 1.7.
+
+When it is removed, the default value of the ``TEST_RUNNER`` setting in
+django.conf.global_settings should be changed to
+``django.test.runner.DjangoTestDiscoverRunner``.
+
+"""
import unittest as real_unittest
+import warnings
-from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
from django.db.models import get_app, get_apps
from django.test import _doctest as doctest
-from django.test.utils import setup_test_environment, teardown_test_environment
+from django.test import runner
from django.test.testcases import OutputChecker, DocTestRunner
from django.utils import unittest
from django.utils.importlib import import_module
@@ -12,6 +19,11 @@
__all__ = ('DjangoTestSuiteRunner')
+warnings.warn(
+ "The django.test.simple module and DjangoTestSuiteRunner are deprecated; "
+ "use django.test.runner.DjangoDiscoverTestRunner instead.",
+ PendingDeprecationWarning)
+
# The module name for tests outside models.py
TEST_MODULE = 'tests'
@@ -153,98 +165,7 @@ def build_test(label):
return unittest.TestSuite(tests)
-def partition_suite(suite, classes, bins):
- """
- Partitions a test suite by test type.
-
- classes is a sequence of types
- bins is a sequence of TestSuites, one more than classes
-
- Tests of type classes[i] are added to bins[i],
- tests with no match found in classes are place in bins[-1]
- """
- for test in suite:
- if isinstance(test, unittest.TestSuite):
- partition_suite(test, classes, bins)
- else:
- for i in range(len(classes)):
- if isinstance(test, classes[i]):
- bins[i].addTest(test)
- break
- else:
- bins[-1].addTest(test)
-
-
-def reorder_suite(suite, classes):
- """
- Reorders a test suite by test type.
-
- `classes` is a sequence of types
-
- All tests of type classes[0] are placed first, then tests of type
- classes[1], etc. Tests with no match in classes are placed last.
- """
- class_count = len(classes)
- bins = [unittest.TestSuite() for i in range(class_count+1)]
- partition_suite(suite, classes, bins)
- for i in range(class_count):
- bins[0].addTests(bins[i+1])
- return bins[0]
-
-
-def dependency_ordered(test_databases, dependencies):
- """
- Reorder test_databases into an order that honors the dependencies
- described in TEST_DEPENDENCIES.
- """
- ordered_test_databases = []
- resolved_databases = set()
-
- # Maps db signature to dependencies of all it's aliases
- dependencies_map = {}
-
- # sanity check - no DB can depend on it's own alias
- for sig, (_, aliases) in test_databases:
- all_deps = set()
- for alias in aliases:
- all_deps.update(dependencies.get(alias, []))
- if not all_deps.isdisjoint(aliases):
- raise ImproperlyConfigured(
- "Circular dependency: databases %r depend on each other, "
- "but are aliases." % aliases)
- dependencies_map[sig] = all_deps
-
- while test_databases:
- changed = False
- deferred = []
-
- # Try to find a DB that has all it's dependencies met
- for signature, (db_name, aliases) in test_databases:
- if dependencies_map[signature].issubset(resolved_databases):
- resolved_databases.update(aliases)
- ordered_test_databases.append((signature, (db_name, aliases)))
- changed = True
- else:
- deferred.append((signature, (db_name, aliases)))
-
- if not changed:
- raise ImproperlyConfigured(
- "Circular dependency in TEST_DEPENDENCIES")
- test_databases = deferred
- return ordered_test_databases
-
-
-class DjangoTestSuiteRunner(object):
- def __init__(self, verbosity=1, interactive=True, failfast=True, **kwargs):
- self.verbosity = verbosity
- self.interactive = interactive
- self.failfast = failfast
-
- def setup_test_environment(self, **kwargs):
- setup_test_environment()
- settings.DEBUG = False
- unittest.installHandler()
-
+class DjangoTestSuiteRunner(runner.DjangoDiscoverTestRunner):
def build_suite(self, test_labels, extra_tests=None, **kwargs):
suite = unittest.TestSuite()
@@ -263,109 +184,4 @@ def build_suite(self, test_labels, extra_tests=None, **kwargs):
for test in extra_tests:
suite.addTest(test)
- return reorder_suite(suite, (unittest.TestCase,))
-
- def setup_databases(self, **kwargs):
- from django.db import connections, DEFAULT_DB_ALIAS
-
- # First pass -- work out which databases actually need to be created,
- # and which ones are test mirrors or duplicate entries in DATABASES
- mirrored_aliases = {}
- test_databases = {}
- dependencies = {}
- for alias in connections:
- connection = connections[alias]
- if connection.settings_dict['TEST_MIRROR']:
- # If the database is marked as a test mirror, save
- # the alias.
- mirrored_aliases[alias] = (
- connection.settings_dict['TEST_MIRROR'])
- else:
- # Store a tuple with DB parameters that uniquely identify it.
- # If we have two aliases with the same values for that tuple,
- # we only need to create the test database once.
- item = test_databases.setdefault(
- connection.creation.test_db_signature(),
- (connection.settings_dict['NAME'], set())
- )
- item[1].add(alias)
-
- if 'TEST_DEPENDENCIES' in connection.settings_dict:
- dependencies[alias] = (
- connection.settings_dict['TEST_DEPENDENCIES'])
- else:
- if alias != DEFAULT_DB_ALIAS:
- dependencies[alias] = connection.settings_dict.get(
- 'TEST_DEPENDENCIES', [DEFAULT_DB_ALIAS])
-
- # Second pass -- actually create the databases.
- old_names = []
- mirrors = []
-
- for signature, (db_name, aliases) in dependency_ordered(
- test_databases.items(), dependencies):
- test_db_name = None
- # Actually create the database for the first connection
-
- for alias in aliases:
- connection = connections[alias]
- old_names.append((connection, db_name, True))
- if test_db_name is None:
- test_db_name = connection.creation.create_test_db(
- self.verbosity, autoclobber=not self.interactive)
- else:
- connection.settings_dict['NAME'] = test_db_name
-
- for alias, mirror_alias in mirrored_aliases.items():
- mirrors.append((alias, connections[alias].settings_dict['NAME']))
- connections[alias].settings_dict['NAME'] = (
- connections[mirror_alias].settings_dict['NAME'])
-
- return old_names, mirrors
-
- def run_suite(self, suite, **kwargs):
- return unittest.TextTestRunner(
- verbosity=self.verbosity, failfast=self.failfast).run(suite)
-
- def teardown_databases(self, old_config, **kwargs):
- """
- Destroys all the non-mirror databases.
- """
- old_names, mirrors = old_config
- for connection, old_name, destroy in old_names:
- if destroy:
- connection.creation.destroy_test_db(old_name, self.verbosity)
-
- def teardown_test_environment(self, **kwargs):
- unittest.removeHandler()
- teardown_test_environment()
-
- def suite_result(self, suite, result, **kwargs):
- return len(result.failures) + len(result.errors)
-
- def run_tests(self, test_labels, extra_tests=None, **kwargs):
- """
- Run the unit tests for all the test labels in the provided list.
- Labels must be of the form:
- - app.TestClass.test_method
- Run a single specific test method
- - app.TestClass
- Run all the test methods in a given class
- - app
- Search for doctests and unittests in the named application.
-
- When looking for tests, the test runner will look in the models and
- tests modules for the application.
-
- A list of 'extra' tests may also be provided; these tests
- will be added to the test suite.
-
- Returns the number of tests that failed.
- """
- self.setup_test_environment()
- suite = self.build_suite(test_labels, extra_tests)
- old_config = self.setup_databases()
- result = self.run_suite(suite)
- self.teardown_databases(old_config)
- self.teardown_test_environment()
- return self.suite_result(suite, result)
+ return runner.reorder_suite(suite, (unittest.TestCase,))
View
6 django/utils/unittest/loader.py
@@ -24,6 +24,7 @@ def _CmpToKey(mycmp):
class K(object):
def __init__(self, obj):
self.obj = obj
+
@akumria
akumria added a note

again with this file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
def __lt__(self, other):
return mycmp(self.obj, other.obj) == -1
return K
@@ -44,9 +45,11 @@ def _make_failed_import_test(name, suiteClass):
return _make_failed_test('ModuleImportFailure', name, ImportError(message),
suiteClass)
+
def _make_failed_load_tests(name, exception, suiteClass):
return _make_failed_test('LoadTestsFailure', name, exception, suiteClass)
+
def _make_failed_test(classname, methodname, exception, suiteClass):
def testFailure(self):
raise exception
@@ -310,13 +313,16 @@ def _makeLoader(prefix, sortUsing, suiteClass=None):
loader.suiteClass = suiteClass
return loader
+
def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp):
return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
+
def makeSuite(testCaseClass, prefix='test', sortUsing=cmp,
suiteClass=suite.TestSuite):
return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass)
+
def findTestCases(module, prefix='test', sortUsing=cmp,
suiteClass=suite.TestSuite):
return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module)
View
4 docs/internals/deprecation.txt
@@ -286,6 +286,10 @@ these changes.
* The ``mimetype`` argument to :class:`~django.http.HttpResponse` ``__init__``
will be removed (``content_type`` should be used instead).
+* The module ``django.test.simple`` and the class
+ ``django.test.simple.DjangoTestSuiteRunner`` will be removed. Instead use
+ `django.test.runner.DjangoTestDiscoverRunner`.
+
2.0
---
View
18 docs/releases/1.5.txt
@@ -49,6 +49,24 @@ field will also get updated on save.
See the :meth:`Model.save() <django.db.models.Model.save()>` documentation for
more details.
+
+Discovery of tests in any test module
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Django 1.5 ships with a new test runner that allows more flexibility in the
+location of tests. With the previous runner
+(``django.test.simple.DjangoTestSuiteRunner``), Django would look for tests
+only in the ``models.py`` and ``tests.py`` modules of any Python package in
+:setting:`INSTALLED_APPS`, and specific tests to be run had to be specified by
+app-label and testcase-class name.
+
+The new runner (``django.test.runner.DjangoTestDiscoverRunner``) will find
+tests by default in any module whose name begins with ``test``, anywhere
+beneath a given root directory. When running ``manage.py test``, the default
+root directory is the current working directory.
+
+For more information, see :doc:`topics/testing`.
+
Caching of related model instances
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
View
0  tests/regressiontests/test_runner/discovery_app/__init__.py
No changes.
View
0  tests/regressiontests/test_runner/discovery_app/models/__init__.py
No changes.
View
16 tests/regressiontests/test_runner/discovery_app/models/test_models.py
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.assertEqual(1 + 1, 2)
View
3  tests/regressiontests/test_runner/discovery_app/tests/__init__.py
@@ -0,0 +1,3 @@
+from test_forms import SimpleTest
+from test_models import SimpleTest
+from views_test import SimpleTest
View
16 tests/regressiontests/test_runner/discovery_app/tests/test_forms.py
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.assertEqual(1 + 1, 2)
View
16 tests/regressiontests/test_runner/discovery_app/tests/test_models.py
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.assertEqual(1 + 1, 2)
View
37 tests/regressiontests/test_runner/discovery_app/tests/views_test.py
@@ -0,0 +1,37 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.assertEqual(1 + 1, 2)
+
+ def test_basic_subtraction(self):
+ """
+ Tests that 2 - 1 always equals 1.
+ """
+ self.assertEqual(2 - 1, 1)
+
+ def test_basic_subtraction_again(self):
+ """
+ Tests that 2 - 1 always equals 1.
+ """
+ self.assertEqual(2 - 1, 1)
+
+
+
+class AnotherSimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.assertEqual(1 + 1, 2)
View
23 tests/regressiontests/test_runner/tests.py
@@ -10,9 +10,11 @@
from django import db
from django.test import simple, TransactionTestCase, skipUnlessDBFeature
from django.test.simple import DjangoTestSuiteRunner, get_tests
+from django.test.runner import DiscoverRunner
from django.test.testcases import connections_support_transactions
from django.utils import unittest
from django.utils.importlib import import_module
+from django.test.utils import override_settings
from ..admin_scripts.tests import AdminScriptTestCase
from .models import Person
@@ -20,6 +22,8 @@
TEST_APP_OK = 'regressiontests.test_runner.valid_app.models'
TEST_APP_ERROR = 'regressiontests.test_runner.invalid_app.models'
+DISCOVER_APP_TESTS = 'regressiontests.test_runner.discovery_app'
+DISCOVER_APP_MODEL_TESTS = 'regressiontests.test_runner.discovery_app.models'
class DependencyOrderingTests(unittest.TestCase):
@@ -279,3 +283,22 @@ def test_autoincrement_reset1(self):
def test_autoincrement_reset2(self):
p = Person.objects.create(first_name='Jack', last_name='Smith')
self.assertEqual(p.pk, 1)
+
+
+class DiscoverTestRunnerTests(AdminScriptTestCase):
+
+ def test_discovery_of_tests_default_settings(self):
+ suite = DiscoverRunner().build_suite((DISCOVER_APP_TESTS, ))
+ discovered_tests = suite.countTestCases()
+ self.assertEqual(3, discovered_tests)
+
+ def test_discovery_of_tests_inside_module(self):
+ suite = DiscoverRunner().build_suite((DISCOVER_APP_MODEL_TESTS, ))
+ discovered_tests = suite.countTestCases()
+ self.assertEqual(1, discovered_tests)
+
+ @override_settings(TEST_DISCOVER_PATTERN='*_test.py')
+ def test_discovery_of_tests_with_pattern(self):
+ suite = DiscoverRunner().build_suite((DISCOVER_APP_TESTS, ))
+ discovered_tests = suite.countTestCases()
+ self.assertEqual(4, discovered_tests)
Something went wrong with that request. Please try again.