mirror of https://github.com/django/django.git
Fixed #12624 -- Modified test runners to be class based.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@12255 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
34e420184c
commit
53b61d9c02
|
@ -487,8 +487,8 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.user_messages.LegacyFallbackS
|
||||||
# TESTING #
|
# TESTING #
|
||||||
###########
|
###########
|
||||||
|
|
||||||
# The name of the method to use to invoke the test suite
|
# The name of the class to use to run the test suite
|
||||||
TEST_RUNNER = 'django.test.simple.run_tests'
|
TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
|
||||||
|
|
||||||
# The name of the database to use for testing purposes.
|
# The name of the database to use for testing purposes.
|
||||||
# If None, a name of 'test_' + DATABASE_NAME will be assumed
|
# If None, a name of 'test_' + DATABASE_NAME will be assumed
|
||||||
|
|
|
@ -21,14 +21,20 @@ class Command(BaseCommand):
|
||||||
verbosity = int(options.get('verbosity', 1))
|
verbosity = int(options.get('verbosity', 1))
|
||||||
interactive = options.get('interactive', True)
|
interactive = options.get('interactive', True)
|
||||||
failfast = options.get('failfast', False)
|
failfast = options.get('failfast', False)
|
||||||
test_runner = get_runner(settings)
|
TestRunner = get_runner(settings)
|
||||||
|
|
||||||
# Some custom test runners won't accept the failfast flag, so let's make sure they accept it before passing it to them
|
if hasattr(TestRunner, 'func_name'):
|
||||||
if 'failfast' in test_runner.func_code.co_varnames:
|
# Pre 1.2 test runners were just functions,
|
||||||
failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive,
|
# and did not support the 'failfast' option.
|
||||||
failfast=failfast)
|
import warnings
|
||||||
|
warnings.warn(
|
||||||
|
'Function-based test runners are deprecated. Test runners should be classes with a run_tests() method.',
|
||||||
|
PendingDeprecationWarning
|
||||||
|
)
|
||||||
|
failures = TestRunner(test_labels, verbosity=verbosity, interactive=interactive)
|
||||||
else:
|
else:
|
||||||
failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
|
test_runner = TestRunner(verbosity=verbosity, interactive=interactive, failfast=failfast)
|
||||||
|
failures = test_runner.run_tests(test_labels)
|
||||||
|
|
||||||
if failures:
|
if failures:
|
||||||
sys.exit(bool(failures))
|
sys.exit(bool(failures))
|
||||||
|
|
|
@ -197,7 +197,60 @@ def reorder_suite(suite, classes):
|
||||||
bins[0].addTests(bins[i+1])
|
bins[0].addTests(bins[i+1])
|
||||||
return bins[0]
|
return bins[0]
|
||||||
|
|
||||||
def run_tests(test_labels, verbosity=1, interactive=True, failfast=False, extra_tests=[]):
|
|
||||||
|
class DjangoTestSuiteRunner(object):
|
||||||
|
def __init__(self, verbosity=1, interactive=True, failfast=True):
|
||||||
|
self.verbosity = verbosity
|
||||||
|
self.interactive = interactive
|
||||||
|
self.failfast = failfast
|
||||||
|
|
||||||
|
def setup_test_environment(self):
|
||||||
|
setup_test_environment()
|
||||||
|
settings.DEBUG = False
|
||||||
|
|
||||||
|
def build_suite(self, test_labels, extra_tests=None):
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
|
||||||
|
if test_labels:
|
||||||
|
for label in test_labels:
|
||||||
|
if '.' in label:
|
||||||
|
suite.addTest(build_test(label))
|
||||||
|
else:
|
||||||
|
app = get_app(label)
|
||||||
|
suite.addTest(build_suite(app))
|
||||||
|
else:
|
||||||
|
for app in get_apps():
|
||||||
|
suite.addTest(build_suite(app))
|
||||||
|
|
||||||
|
if extra_tests:
|
||||||
|
for test in extra_tests:
|
||||||
|
suite.addTest(test)
|
||||||
|
|
||||||
|
return reorder_suite(suite, (TestCase,))
|
||||||
|
|
||||||
|
def setup_databases(self):
|
||||||
|
from django.db import connections
|
||||||
|
old_names = []
|
||||||
|
for alias in connections:
|
||||||
|
connection = connections[alias]
|
||||||
|
old_names.append((connection, connection.settings_dict['NAME']))
|
||||||
|
connection.creation.create_test_db(self.verbosity, autoclobber=not self.interactive)
|
||||||
|
return old_names
|
||||||
|
|
||||||
|
def run_suite(self, suite):
|
||||||
|
return DjangoTestRunner(verbosity=self.verbosity, failfast=self.failfast).run(suite)
|
||||||
|
|
||||||
|
def teardown_databases(self, old_names):
|
||||||
|
for connection, old_name in old_names:
|
||||||
|
connection.creation.destroy_test_db(old_name, self.verbosity)
|
||||||
|
|
||||||
|
def teardown_test_environment(self):
|
||||||
|
teardown_test_environment()
|
||||||
|
|
||||||
|
def suite_result(self, result):
|
||||||
|
return len(result.failures) + len(result.errors)
|
||||||
|
|
||||||
|
def run_tests(self, test_labels, extra_tests=None):
|
||||||
"""
|
"""
|
||||||
Run the unit tests for all the test labels in the provided list.
|
Run the unit tests for all the test labels in the provided list.
|
||||||
Labels must be of the form:
|
Labels must be of the form:
|
||||||
|
@ -216,37 +269,25 @@ def run_tests(test_labels, verbosity=1, interactive=True, failfast=False, extra_
|
||||||
|
|
||||||
Returns the number of tests that failed.
|
Returns the number of tests that failed.
|
||||||
"""
|
"""
|
||||||
setup_test_environment()
|
self.setup_test_environment()
|
||||||
|
|
||||||
settings.DEBUG = False
|
old_names = self.setup_databases()
|
||||||
suite = unittest.TestSuite()
|
|
||||||
|
|
||||||
if test_labels:
|
suite = self.build_suite(test_labels, extra_tests)
|
||||||
for label in test_labels:
|
|
||||||
if '.' in label:
|
|
||||||
suite.addTest(build_test(label))
|
|
||||||
else:
|
|
||||||
app = get_app(label)
|
|
||||||
suite.addTest(build_suite(app))
|
|
||||||
else:
|
|
||||||
for app in get_apps():
|
|
||||||
suite.addTest(build_suite(app))
|
|
||||||
|
|
||||||
for test in extra_tests:
|
result = self.run_suite(suite)
|
||||||
suite.addTest(test)
|
|
||||||
|
|
||||||
suite = reorder_suite(suite, (TestCase,))
|
self.teardown_databases(old_names)
|
||||||
|
|
||||||
from django.db import connections
|
self.teardown_test_environment()
|
||||||
old_names = []
|
|
||||||
for alias in connections:
|
|
||||||
connection = connections[alias]
|
|
||||||
old_names.append((connection, connection.settings_dict['NAME']))
|
|
||||||
connection.creation.create_test_db(verbosity, autoclobber=not interactive)
|
|
||||||
result = DjangoTestRunner(verbosity=verbosity, failfast=failfast).run(suite)
|
|
||||||
for connection, old_name in old_names:
|
|
||||||
connection.creation.destroy_test_db(old_name, verbosity)
|
|
||||||
|
|
||||||
teardown_test_environment()
|
return self.suite_result(result)
|
||||||
|
|
||||||
return len(result.failures) + len(result.errors)
|
def run_tests(test_labels, verbosity=1, interactive=True, failfast=False, extra_tests=None):
|
||||||
|
import warnings
|
||||||
|
warnings.warn(
|
||||||
|
'The run_tests() test runner has been deprecated in favor of DjangoTestSuiteRunner.',
|
||||||
|
PendingDeprecationWarning
|
||||||
|
)
|
||||||
|
test_runner = DjangoTestSuiteRunner(verbosity=verbosity, interactive=interactive, failfast=failfast)
|
||||||
|
return test_runner.run_tests(test_labels, extra_tests=extra_tests)
|
||||||
|
|
|
@ -74,6 +74,9 @@ their deprecation, as per the :ref:`Django deprecation policy
|
||||||
``django.utils.formats.get_format()`` to get the appropriate
|
``django.utils.formats.get_format()`` to get the appropriate
|
||||||
formats.
|
formats.
|
||||||
|
|
||||||
|
* The ability to use a function-based test runners will be removed,
|
||||||
|
along with the ``django.test.simple.run_tests()`` test runner.
|
||||||
|
|
||||||
* 2.0
|
* 2.0
|
||||||
* ``django.views.defaults.shortcut()``. This function has been moved
|
* ``django.views.defaults.shortcut()``. This function has been moved
|
||||||
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
|
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
|
||||||
|
|
|
@ -367,6 +367,14 @@ An undocumented regex for validating email addresses has been moved from
|
||||||
django.form.fields to django.core.validators. You will need to update
|
django.form.fields to django.core.validators. You will need to update
|
||||||
your imports if you are using it.
|
your imports if you are using it.
|
||||||
|
|
||||||
|
Function-based test runners
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Django 1.2 changes the test runner tools to use a class-based
|
||||||
|
approach. Old style function-based test runners will still work, but
|
||||||
|
should be updated to use the new :ref:`class-based runners
|
||||||
|
<topics-testing-test_runner>`.
|
||||||
|
|
||||||
What's new in Django 1.2
|
What's new in Django 1.2
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
|
|
@ -1228,41 +1228,65 @@ alternative framework as if they were normal Django tests.
|
||||||
|
|
||||||
When you run ``./manage.py test``, Django looks at the :setting:`TEST_RUNNER`
|
When you run ``./manage.py test``, Django looks at the :setting:`TEST_RUNNER`
|
||||||
setting to determine what to do. By default, :setting:`TEST_RUNNER` points to
|
setting to determine what to do. By default, :setting:`TEST_RUNNER` points to
|
||||||
``'django.test.simple.run_tests'``. This method defines the default Django
|
``'django.test.simple.DjangoTestSuiteRunner'``. This class defines the default Django
|
||||||
testing behavior. This behavior involves:
|
testing behavior. This behavior involves:
|
||||||
|
|
||||||
#. Performing global pre-test setup.
|
#. Performing global pre-test setup.
|
||||||
|
|
||||||
#. Creating the test database.
|
#. Creating the test databases.
|
||||||
|
|
||||||
#. Running ``syncdb`` to install models and initial data into the test
|
#. Running ``syncdb`` to install models and initial data into the test
|
||||||
database.
|
databases.
|
||||||
|
|
||||||
#. Looking for unit tests and doctests in the ``models.py`` and
|
#. Looking for unit tests and doctests in the ``models.py`` and
|
||||||
``tests.py`` files in each installed application.
|
``tests.py`` files in each installed application.
|
||||||
|
|
||||||
#. Running the unit tests and doctests that are found.
|
#. Running the unit tests and doctests that are found.
|
||||||
|
|
||||||
#. Destroying the test database.
|
#. Destroying the test databases.
|
||||||
|
|
||||||
#. Performing global post-test teardown.
|
#. Performing global post-test teardown.
|
||||||
|
|
||||||
If you define your own test runner method and point :setting:`TEST_RUNNER` at
|
If you define your own test runner method and point :setting:`TEST_RUNNER` at
|
||||||
that method, Django will execute your test runner whenever you run
|
that method, Django will execute your test runner whenever you run
|
||||||
``./manage.py test``. In this way, it is possible to use any test framework
|
``./manage.py test``. In this way, it is possible to use any test framework
|
||||||
that can be executed from Python code.
|
that can be executed from Python code, or to modify the Django test execution
|
||||||
|
process to satisfy whatever testing requirements you may have.
|
||||||
|
|
||||||
|
.. _topics-testing-test_runner:
|
||||||
|
|
||||||
Defining a test runner
|
Defining a test runner
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
.. versionadded:: 1.0
|
.. versionchanged:: 1.2
|
||||||
|
Prior to 1.2, test runners were a single function, not a class.
|
||||||
|
|
||||||
.. currentmodule:: django.test.simple
|
.. currentmodule:: django.test.simple
|
||||||
|
|
||||||
By convention, a test runner should be called ``run_tests``. The only strict
|
A test runner is a class defining a ``run_tests()`` method. Django ships
|
||||||
requirement is that it has the same arguments as the Django test runner:
|
with a ``DjangoTestSuiteRunner`` class that defines the default Django
|
||||||
|
testing behavior. This class defines the ``run_tests()`` entry point,
|
||||||
|
plus a selection of other methods that are used to by ``run_tests()`` to
|
||||||
|
set up, execute and tear down the test suite.
|
||||||
|
|
||||||
.. function:: run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[])
|
.. class:: DjangoTestSuiteRunner(verbosity=1, interactive=True, failfast=True)
|
||||||
|
|
||||||
|
``verbosity`` determines the amount of notification and debug information
|
||||||
|
that will be printed to the console; ``0`` is no output, ``1`` is normal
|
||||||
|
output, and ``2`` is verbose output.
|
||||||
|
|
||||||
|
If ``interactive`` is ``True``, the test suite has permission to ask the
|
||||||
|
user for instructions when the test suite is executed. An example of this
|
||||||
|
behavior would be asking for permission to delete an existing test
|
||||||
|
database. If ``interactive`` is ``False``, the test suite must be able to
|
||||||
|
run without any manual intervention.
|
||||||
|
|
||||||
|
If ``failfast`` is ``True``, the test suite will stop running after the
|
||||||
|
first test failure is detected.
|
||||||
|
|
||||||
|
.. method:: DjangoTestSuiteRunner.run_tests(test_labels, extra_tests=[])
|
||||||
|
|
||||||
|
Run the test suite.
|
||||||
|
|
||||||
``test_labels`` is a list of strings describing the tests to be run. A test
|
``test_labels`` is a list of strings describing the tests to be run. A test
|
||||||
label can take one of three forms:
|
label can take one of three forms:
|
||||||
|
@ -1275,21 +1299,61 @@ requirement is that it has the same arguments as the Django test runner:
|
||||||
If ``test_labels`` has a value of ``None``, the test runner should run
|
If ``test_labels`` has a value of ``None``, the test runner should run
|
||||||
search for tests in all the applications in :setting:`INSTALLED_APPS`.
|
search for tests in all the applications in :setting:`INSTALLED_APPS`.
|
||||||
|
|
||||||
``verbosity`` determines the amount of notification and debug information
|
``extra_tests`` is a list of extra ``TestCase`` instances to add to the
|
||||||
that will be printed to the console; ``0`` is no output, ``1`` is normal
|
suite that is executed by the test runner. These extra tests are run
|
||||||
output, and ``2`` is verbose output.
|
in addition to those discovered in the modules listed in ``test_labels``.
|
||||||
|
|
||||||
If ``interactive`` is ``True``, the test suite has permission to ask the
|
This method should return the number of tests that failed.
|
||||||
user for instructions when the test suite is executed. An example of this
|
|
||||||
behavior would be asking for permission to delete an existing test
|
.. method:: DjangoTestSuiteRunner.setup_test_environment()
|
||||||
database. If ``interactive`` is ``False``, the test suite must be able to
|
|
||||||
run without any manual intervention.
|
Sets up the test environment ready for testing.
|
||||||
|
|
||||||
|
.. method:: DjangoTestSuiteRunner.build_suite(test_labels, extra_tests=[])
|
||||||
|
|
||||||
|
Constructs a test suite that matches the test labels provided.
|
||||||
|
|
||||||
|
``test_labels`` is a list of strings describing the tests to be run. A test
|
||||||
|
label can take one of three forms:
|
||||||
|
|
||||||
|
* ``app.TestCase.test_method`` -- Run a single test method in a test
|
||||||
|
case.
|
||||||
|
* ``app.TestCase`` -- Run all the test methods in a test case.
|
||||||
|
* ``app`` -- Search for and run all tests in the named application.
|
||||||
|
|
||||||
|
If ``test_labels`` has a value of ``None``, the test runner should run
|
||||||
|
search for tests in all the applications in :setting:`INSTALLED_APPS`.
|
||||||
|
|
||||||
``extra_tests`` is a list of extra ``TestCase`` instances to add to the
|
``extra_tests`` is a list of extra ``TestCase`` instances to add to the
|
||||||
suite that is executed by the test runner. These extra tests are run
|
suite that is executed by the test runner. These extra tests are run
|
||||||
in addition to those discovered in the modules listed in ``module_list``.
|
in addition to those discovered in the modules listed in ``test_labels``.
|
||||||
|
|
||||||
|
Returns a ``TestSuite`` instance ready to be run.
|
||||||
|
|
||||||
|
.. method:: DjangoTestSuiteRunner.setup_databases()
|
||||||
|
|
||||||
|
Creates the test databases.
|
||||||
|
|
||||||
|
Returns the list of old database names that will need to be restored
|
||||||
|
|
||||||
|
.. method:: DjangoTestSuiteRunner.run_suite(suite)
|
||||||
|
|
||||||
|
Runs the test suite.
|
||||||
|
|
||||||
|
Returns the result produced by the running the test suite.
|
||||||
|
|
||||||
|
.. method:: DjangoTestSuiteRunner.teardown_databases(old_names)
|
||||||
|
|
||||||
|
Destroys the test databases, restoring the old names.
|
||||||
|
|
||||||
|
.. method:: DjangoTestSuiteRunner.teardown_test_environment()
|
||||||
|
|
||||||
|
Restores the pre-test environment.
|
||||||
|
|
||||||
|
.. method:: DjangoTestSuiteRunner.suite_result(result)
|
||||||
|
|
||||||
|
Computes and returns a return code based on a test suite result.
|
||||||
|
|
||||||
This method should return the number of tests that failed.
|
|
||||||
|
|
||||||
Testing utilities
|
Testing utilities
|
||||||
-----------------
|
-----------------
|
||||||
|
|
|
@ -156,11 +156,23 @@ def django_tests(verbosity, interactive, failfast, test_labels):
|
||||||
# Run the test suite, including the extra validation tests.
|
# Run the test suite, including the extra validation tests.
|
||||||
from django.test.utils import get_runner
|
from django.test.utils import get_runner
|
||||||
if not hasattr(settings, 'TEST_RUNNER'):
|
if not hasattr(settings, 'TEST_RUNNER'):
|
||||||
settings.TEST_RUNNER = 'django.test.simple.run_tests'
|
settings.TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
|
||||||
test_runner = get_runner(settings)
|
TestRunner = get_runner(settings)
|
||||||
|
|
||||||
failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, failfast=failfast,
|
if hasattr(TestRunner, 'func_name'):
|
||||||
|
# Pre 1.2 test runners were just functions,
|
||||||
|
# and did not support the 'failfast' option.
|
||||||
|
import warnings
|
||||||
|
warnings.warn(
|
||||||
|
'Function-based test runners are deprecated. Test runners should be classes with a run_tests() method.',
|
||||||
|
PendingDeprecationWarning
|
||||||
|
)
|
||||||
|
failures = TestRunner(test_labels, verbosity=verbosity, interactive=interactive,
|
||||||
extra_tests=extra_tests)
|
extra_tests=extra_tests)
|
||||||
|
else:
|
||||||
|
test_runner = TestRunner(verbosity=verbosity, interactive=interactive, failfast=failfast)
|
||||||
|
failures = test_runner.run_tests(test_labels, extra_tests=extra_tests)
|
||||||
|
|
||||||
if failures:
|
if failures:
|
||||||
sys.exit(bool(failures))
|
sys.exit(bool(failures))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue