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 #
|
||||
###########
|
||||
|
||||
# The name of the method to use to invoke the test suite
|
||||
TEST_RUNNER = 'django.test.simple.run_tests'
|
||||
# The name of the class to use to run the test suite
|
||||
TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
|
||||
|
||||
# The name of the database to use for testing purposes.
|
||||
# If None, a name of 'test_' + DATABASE_NAME will be assumed
|
||||
|
|
|
@ -21,14 +21,20 @@ class Command(BaseCommand):
|
|||
verbosity = int(options.get('verbosity', 1))
|
||||
interactive = options.get('interactive', True)
|
||||
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 'failfast' in test_runner.func_code.co_varnames:
|
||||
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)
|
||||
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:
|
||||
sys.exit(bool(failures))
|
||||
|
|
|
@ -40,7 +40,7 @@ class DjangoTestRunner(unittest.TextTestRunner):
|
|||
"""
|
||||
self._keyboard_interrupt_intercepted = True
|
||||
sys.stderr.write(" <Test run halted by Ctrl-C> ")
|
||||
# Set the interrupt handler back to the default handler, so that
|
||||
# Set the interrupt handler back to the default handler, so that
|
||||
# another Ctrl-C press will trigger immediate exit.
|
||||
signal.signal(signal.SIGINT, self._default_keyboard_interrupt_handler)
|
||||
|
||||
|
@ -197,56 +197,97 @@ def reorder_suite(suite, classes):
|
|||
bins[0].addTests(bins[i+1])
|
||||
return bins[0]
|
||||
|
||||
def run_tests(test_labels, verbosity=1, interactive=True, failfast=False, extra_tests=[]):
|
||||
"""
|
||||
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.
|
||||
class DjangoTestSuiteRunner(object):
|
||||
def __init__(self, verbosity=1, interactive=True, failfast=True):
|
||||
self.verbosity = verbosity
|
||||
self.interactive = interactive
|
||||
self.failfast = failfast
|
||||
|
||||
A list of 'extra' tests may also be provided; these tests
|
||||
will be added to the test suite.
|
||||
def setup_test_environment(self):
|
||||
setup_test_environment()
|
||||
settings.DEBUG = False
|
||||
|
||||
Returns the number of tests that failed.
|
||||
"""
|
||||
setup_test_environment()
|
||||
def build_suite(self, test_labels, extra_tests=None):
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
settings.DEBUG = False
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
if test_labels:
|
||||
for label in test_labels:
|
||||
if '.' in label:
|
||||
suite.addTest(build_test(label))
|
||||
else:
|
||||
app = get_app(label)
|
||||
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))
|
||||
else:
|
||||
for app in get_apps():
|
||||
suite.addTest(build_suite(app))
|
||||
|
||||
for test in extra_tests:
|
||||
suite.addTest(test)
|
||||
if extra_tests:
|
||||
for test in extra_tests:
|
||||
suite.addTest(test)
|
||||
|
||||
suite = reorder_suite(suite, (TestCase,))
|
||||
return reorder_suite(suite, (TestCase,))
|
||||
|
||||
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(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)
|
||||
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
|
||||
|
||||
teardown_test_environment()
|
||||
def run_suite(self, suite):
|
||||
return DjangoTestRunner(verbosity=self.verbosity, failfast=self.failfast).run(suite)
|
||||
|
||||
return len(result.failures) + len(result.errors)
|
||||
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.
|
||||
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()
|
||||
|
||||
old_names = self.setup_databases()
|
||||
|
||||
suite = self.build_suite(test_labels, extra_tests)
|
||||
|
||||
result = self.run_suite(suite)
|
||||
|
||||
self.teardown_databases(old_names)
|
||||
|
||||
self.teardown_test_environment()
|
||||
|
||||
return self.suite_result(result)
|
||||
|
||||
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
|
||||
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
|
||||
* ``django.views.defaults.shortcut()``. This function has been moved
|
||||
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
|
||||
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
|
||||
========================
|
||||
|
||||
|
@ -428,7 +436,7 @@ added support for comparison operators. No longer will you have to type:
|
|||
.. code-block:: html+django
|
||||
|
||||
{% ifnotequal a b %}
|
||||
...
|
||||
...
|
||||
{% endifnotequal %}
|
||||
|
||||
You can now do this::
|
||||
|
@ -436,7 +444,7 @@ You can now do this::
|
|||
.. code-block:: html+django
|
||||
|
||||
{% if a != b %}
|
||||
...
|
||||
...
|
||||
{% endif %}
|
||||
|
||||
There's really no reason to use ``{% ifequal %}`` or ``{% ifnotequal %}``
|
||||
|
|
|
@ -275,15 +275,15 @@ a test case, add the name of the test method to the label::
|
|||
$ ./manage.py test animals.AnimalTestCase.testFluffyAnimals
|
||||
|
||||
.. versionadded:: 1.2
|
||||
You can now trigger a graceful exit from a test run by pressing ``Ctrl-C``.
|
||||
You can now trigger a graceful exit from a test run by pressing ``Ctrl-C``.
|
||||
|
||||
If you press ``Ctrl-C`` while the tests are running, the test runner will
|
||||
If you press ``Ctrl-C`` while the tests are running, the test runner will
|
||||
wait for the currently running test to complete and then exit gracefully.
|
||||
During a graceful exit the test runner will output details of any test
|
||||
During a graceful exit the test runner will output details of any test
|
||||
failures, report on how many tests were run and how many errors and failures
|
||||
were encountered, and destroy any test databases as usual. Thus pressing
|
||||
were encountered, and destroy any test databases as usual. Thus pressing
|
||||
``Ctrl-C`` can be very useful if you forget to pass the :djadminopt:`--failfast`
|
||||
option, notice that some tests are unexpectedly failing, and want to get details
|
||||
option, notice that some tests are unexpectedly failing, and want to get details
|
||||
on the failures without waiting for the full test run to complete.
|
||||
|
||||
If you do not want to wait for the currently running test to finish, you
|
||||
|
@ -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`
|
||||
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:
|
||||
|
||||
#. Performing global pre-test setup.
|
||||
|
||||
#. Creating the test database.
|
||||
#. Creating the test databases.
|
||||
|
||||
#. Running ``syncdb`` to install models and initial data into the test
|
||||
database.
|
||||
databases.
|
||||
|
||||
#. Looking for unit tests and doctests in the ``models.py`` and
|
||||
``tests.py`` files in each installed application.
|
||||
|
||||
#. Running the unit tests and doctests that are found.
|
||||
|
||||
#. Destroying the test database.
|
||||
#. Destroying the test databases.
|
||||
|
||||
#. Performing global post-test teardown.
|
||||
|
||||
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
|
||||
``./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
|
||||
----------------------
|
||||
|
||||
.. versionadded:: 1.0
|
||||
.. versionchanged:: 1.2
|
||||
Prior to 1.2, test runners were a single function, not a class.
|
||||
|
||||
.. currentmodule:: django.test.simple
|
||||
|
||||
By convention, a test runner should be called ``run_tests``. The only strict
|
||||
requirement is that it has the same arguments as the Django test runner:
|
||||
A test runner is a class defining a ``run_tests()`` method. Django ships
|
||||
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
|
||||
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
|
||||
search for tests in all the applications in :setting:`INSTALLED_APPS`.
|
||||
|
||||
``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.
|
||||
``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
|
||||
in addition to those discovered in the modules listed in ``test_labels``.
|
||||
|
||||
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.
|
||||
This method should return the number of tests that failed.
|
||||
|
||||
.. method:: DjangoTestSuiteRunner.setup_test_environment()
|
||||
|
||||
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
|
||||
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
|
||||
-----------------
|
||||
|
|
|
@ -156,11 +156,23 @@ def django_tests(verbosity, interactive, failfast, test_labels):
|
|||
# Run the test suite, including the extra validation tests.
|
||||
from django.test.utils import get_runner
|
||||
if not hasattr(settings, 'TEST_RUNNER'):
|
||||
settings.TEST_RUNNER = 'django.test.simple.run_tests'
|
||||
test_runner = get_runner(settings)
|
||||
settings.TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
|
||||
TestRunner = get_runner(settings)
|
||||
|
||||
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)
|
||||
else:
|
||||
test_runner = TestRunner(verbosity=verbosity, interactive=interactive, failfast=failfast)
|
||||
failures = test_runner.run_tests(test_labels, extra_tests=extra_tests)
|
||||
|
||||
failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, failfast=failfast,
|
||||
extra_tests=extra_tests)
|
||||
if failures:
|
||||
sys.exit(bool(failures))
|
||||
|
||||
|
|
Loading…
Reference in New Issue