Fixed #4460 -- Added the ability to be more specific in the test cases that are executed. This is a backwards incompatible change for any user with a custom test runner. See the wiki for details.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5769 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
5b8d2c9f0d
commit
650cea9170
|
@ -1331,16 +1331,11 @@ def runfcgi(args):
|
||||||
runfastcgi(args)
|
runfastcgi(args)
|
||||||
runfcgi.args = '[various KEY=val options, use `runfcgi help` for help]'
|
runfcgi.args = '[various KEY=val options, use `runfcgi help` for help]'
|
||||||
|
|
||||||
def test(app_labels, verbosity=1, interactive=True):
|
def test(test_labels, verbosity=1, interactive=True):
|
||||||
"Runs the test suite for the specified applications"
|
"Runs the test suite for the specified applications"
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import get_app, get_apps
|
from django.db.models import get_app, get_apps
|
||||||
|
|
||||||
if len(app_labels) == 0:
|
|
||||||
app_list = get_apps()
|
|
||||||
else:
|
|
||||||
app_list = [get_app(app_label) for app_label in app_labels]
|
|
||||||
|
|
||||||
test_path = settings.TEST_RUNNER.split('.')
|
test_path = settings.TEST_RUNNER.split('.')
|
||||||
# Allow for Python 2.5 relative paths
|
# Allow for Python 2.5 relative paths
|
||||||
if len(test_path) > 1:
|
if len(test_path) > 1:
|
||||||
|
@ -1350,7 +1345,7 @@ def test(app_labels, verbosity=1, interactive=True):
|
||||||
test_module = __import__(test_module_name, {}, {}, test_path[-1])
|
test_module = __import__(test_module_name, {}, {}, test_path[-1])
|
||||||
test_runner = getattr(test_module, test_path[-1])
|
test_runner = getattr(test_module, test_path[-1])
|
||||||
|
|
||||||
failures = test_runner(app_list, verbosity=verbosity, interactive=interactive)
|
failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
|
||||||
if failures:
|
if failures:
|
||||||
sys.exit(failures)
|
sys.exit(failures)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.models import get_app, get_apps
|
||||||
from django.test import _doctest as doctest
|
from django.test import _doctest as doctest
|
||||||
from django.test.utils import setup_test_environment, teardown_test_environment
|
from django.test.utils import setup_test_environment, teardown_test_environment
|
||||||
from django.test.utils import create_test_db, destroy_test_db
|
from django.test.utils import create_test_db, destroy_test_db
|
||||||
|
@ -10,6 +11,31 @@ TEST_MODULE = 'tests'
|
||||||
|
|
||||||
doctestOutputChecker = OutputChecker()
|
doctestOutputChecker = OutputChecker()
|
||||||
|
|
||||||
|
def get_tests(app_module):
|
||||||
|
try:
|
||||||
|
app_path = app_module.__name__.split('.')[:-1]
|
||||||
|
test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE)
|
||||||
|
except ImportError, e:
|
||||||
|
# Couldn't import tests.py. Was it due to a missing file, or
|
||||||
|
# due to an import error in a tests.py that actually exists?
|
||||||
|
import os.path
|
||||||
|
from imp import find_module
|
||||||
|
try:
|
||||||
|
mod = find_module(TEST_MODULE, [os.path.dirname(app_module.__file__)])
|
||||||
|
except ImportError:
|
||||||
|
# 'tests' module doesn't exist. Move on.
|
||||||
|
test_module = None
|
||||||
|
else:
|
||||||
|
# The module exists, so there must be an import error in the
|
||||||
|
# test module itself. We don't need the module; so if the
|
||||||
|
# module was a single file module (i.e., tests.py), close the file
|
||||||
|
# handle returned by find_module. Otherwise, the test module
|
||||||
|
# is a directory, and there is nothing to close.
|
||||||
|
if mod[0]:
|
||||||
|
mod[0].close()
|
||||||
|
raise
|
||||||
|
return test_module
|
||||||
|
|
||||||
def build_suite(app_module):
|
def build_suite(app_module):
|
||||||
"Create a complete Django test suite for the provided application module"
|
"Create a complete Django test suite for the provided application module"
|
||||||
suite = unittest.TestSuite()
|
suite = unittest.TestSuite()
|
||||||
|
@ -30,10 +56,8 @@ def build_suite(app_module):
|
||||||
|
|
||||||
# Check to see if a separate 'tests' module exists parallel to the
|
# Check to see if a separate 'tests' module exists parallel to the
|
||||||
# models module
|
# models module
|
||||||
try:
|
test_module = get_tests(app_module)
|
||||||
app_path = app_module.__name__.split('.')[:-1]
|
if test_module:
|
||||||
test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE)
|
|
||||||
|
|
||||||
# Load unit and doctests in the tests.py module. If module has
|
# Load unit and doctests in the tests.py module. If module has
|
||||||
# a suite() method, use it. Otherwise build the test suite ourselves.
|
# a suite() method, use it. Otherwise build the test suite ourselves.
|
||||||
if hasattr(test_module, 'suite'):
|
if hasattr(test_module, 'suite'):
|
||||||
|
@ -47,34 +71,50 @@ def build_suite(app_module):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# No doc tests in tests.py
|
# No doc tests in tests.py
|
||||||
pass
|
pass
|
||||||
except ImportError, e:
|
|
||||||
# Couldn't import tests.py. Was it due to a missing file, or
|
|
||||||
# due to an import error in a tests.py that actually exists?
|
|
||||||
import os.path
|
|
||||||
from imp import find_module
|
|
||||||
try:
|
|
||||||
mod = find_module(TEST_MODULE, [os.path.dirname(app_module.__file__)])
|
|
||||||
except ImportError:
|
|
||||||
# 'tests' module doesn't exist. Move on.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# The module exists, so there must be an import error in the
|
|
||||||
# test module itself. We don't need the module; so if the
|
|
||||||
# module was a single file module (i.e., tests.py), close the file
|
|
||||||
# handle returned by find_module. Otherwise, the test module
|
|
||||||
# is a directory, and there is nothing to close.
|
|
||||||
if mod[0]:
|
|
||||||
mod[0].close()
|
|
||||||
raise
|
|
||||||
|
|
||||||
return suite
|
return suite
|
||||||
|
|
||||||
def run_tests(module_list, verbosity=1, interactive=True, extra_tests=[]):
|
def build_test(label):
|
||||||
|
"""Construct a test case a test with the specified label. Label should
|
||||||
|
be of the form model.TestClass or model.TestClass.test_method. Returns
|
||||||
|
an instantiated test or test suite corresponding to the label provided.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Run the unit tests for all the modules in the provided list.
|
parts = label.split('.')
|
||||||
This testrunner will search each of the modules in the provided list,
|
if len(parts) < 2 or len(parts) > 3:
|
||||||
looking for doctests and unittests in models.py or tests.py within
|
raise ValueError("Test label '%s' should be of the form app.TestCase or app.TestCase.test_method" % label)
|
||||||
the module. A list of 'extra' tests may also be provided; these tests
|
|
||||||
|
app_module = get_app(parts[0])
|
||||||
|
TestClass = getattr(app_module, parts[1], None)
|
||||||
|
|
||||||
|
# Couldn't find the test class in models.py; look in tests.py
|
||||||
|
if TestClass is None:
|
||||||
|
test_module = get_tests(app_module)
|
||||||
|
if test_module:
|
||||||
|
TestClass = getattr(test_module, parts[1], None)
|
||||||
|
|
||||||
|
if len(parts) == 2: # label is app.TestClass
|
||||||
|
try:
|
||||||
|
return unittest.TestLoader().loadTestsFromTestCase(TestClass)
|
||||||
|
except TypeError:
|
||||||
|
raise ValueError("Test label '%s' does not refer to a test class" % label)
|
||||||
|
else: # label is app.TestClass.test_method
|
||||||
|
return TestClass(parts[2])
|
||||||
|
|
||||||
|
def run_tests(test_labels, verbosity=1, interactive=True, 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.
|
||||||
|
|
||||||
|
A list of 'extra' tests may also be provided; these tests
|
||||||
will be added to the test suite.
|
will be added to the test suite.
|
||||||
|
|
||||||
Returns the number of tests that failed.
|
Returns the number of tests that failed.
|
||||||
|
@ -83,9 +123,17 @@ def run_tests(module_list, verbosity=1, interactive=True, extra_tests=[]):
|
||||||
|
|
||||||
settings.DEBUG = False
|
settings.DEBUG = False
|
||||||
suite = unittest.TestSuite()
|
suite = unittest.TestSuite()
|
||||||
|
|
||||||
for module in module_list:
|
if test_labels:
|
||||||
suite.addTest(build_suite(module))
|
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:
|
for test in extra_tests:
|
||||||
suite.addTest(test)
|
suite.addTest(test)
|
||||||
|
|
|
@ -450,6 +450,9 @@ look like::
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# test definitions as before
|
# test definitions as before
|
||||||
|
|
||||||
|
def testFluffyAnimals(self):
|
||||||
|
# A test that uses the fixtures
|
||||||
|
|
||||||
At the start of each test case, before ``setUp()`` is run, Django will
|
At the start of each test case, before ``setUp()`` is run, Django will
|
||||||
flush the database, returning the database the state it was in directly
|
flush the database, returning the database the state it was in directly
|
||||||
after ``syncdb`` was called. Then, all the named fixtures are installed.
|
after ``syncdb`` was called. Then, all the named fixtures are installed.
|
||||||
|
@ -483,8 +486,8 @@ that can be useful in testing the behavior of web sites.
|
||||||
|
|
||||||
``assertContains(response, text, count=None, status_code=200)``
|
``assertContains(response, text, count=None, status_code=200)``
|
||||||
Assert that a response indicates that a page could be retrieved and
|
Assert that a response indicates that a page could be retrieved and
|
||||||
produced the nominated status code, and that ``text`` in the content
|
produced the nominated status code, and that ``text`` in the content
|
||||||
of the response. If ``count`` is provided, ``text`` must occur exactly
|
of the response. If ``count`` is provided, ``text`` must occur exactly
|
||||||
``count`` times in the response.
|
``count`` times in the response.
|
||||||
|
|
||||||
``assertFormError(response, form, field, errors)``
|
``assertFormError(response, form, field, errors)``
|
||||||
|
@ -571,6 +574,18 @@ but you only want to run the animals unit tests, run::
|
||||||
|
|
||||||
$ ./manage.py test animals
|
$ ./manage.py test animals
|
||||||
|
|
||||||
|
**New in Django development version:** If you use unit tests, you can be more
|
||||||
|
specific in the tests that are executed. To run a single test case in an
|
||||||
|
application (for example, the AnimalTestCase described previously), add the
|
||||||
|
name of the test case to the label on the command line::
|
||||||
|
|
||||||
|
$ ./manage.py test animals.AnimalTestCase
|
||||||
|
|
||||||
|
**New in Django development version:**To run a single test method inside a
|
||||||
|
test case, add the name of the test method to the label::
|
||||||
|
|
||||||
|
$ ./manage.py test animals.AnimalTestCase.testFluffyAnimals
|
||||||
|
|
||||||
When you run your tests, you'll see a bunch of text flow by as the test
|
When you run your tests, you'll see a bunch of text flow by as the test
|
||||||
database is created and models are initialized. This test database is
|
database is created and models are initialized. This test database is
|
||||||
created from scratch every time you run your tests.
|
created from scratch every time you run your tests.
|
||||||
|
@ -665,25 +680,30 @@ By convention, a test runner should be called ``run_tests``; however, you
|
||||||
can call it anything you want. The only requirement is that it has the
|
can call it anything you want. The only requirement is that it has the
|
||||||
same arguments as the Django test runner:
|
same arguments as the Django test runner:
|
||||||
|
|
||||||
``run_tests(module_list, verbosity=1, interactive=True, extra_tests=[])``
|
``run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[])``
|
||||||
The module list is the list of Python modules that contain the models to be
|
**New in Django development version:** ``test_labels`` is a list of
|
||||||
tested. This is the same format returned by ``django.db.models.get_apps()``.
|
strings describing the tests to be run. A test label can take one of
|
||||||
The test runner should search these modules for tests to execute.
|
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 ``INSTALLED_APPS``.
|
||||||
|
|
||||||
Verbosity determines the amount of notification and debug information that
|
Verbosity determines the amount of notification and debug information that
|
||||||
will be printed to the console; ``0`` is no output, ``1`` is normal output,
|
will be printed to the console; ``0`` is no output, ``1`` is normal output,
|
||||||
and ``2`` is verbose output.
|
and ``2`` is verbose output.
|
||||||
|
|
||||||
**New in Django development version** If ``interactive`` is ``True``, the
|
**New in Django development version:** If ``interactive`` is ``True``, the
|
||||||
test suite may ask the user for instructions when the test suite is
|
test suite may ask the user for instructions when the test suite is
|
||||||
executed. An example of this behavior would be asking for permission to
|
executed. An example of this behavior would be asking for permission to
|
||||||
delete an existing test database. If ``interactive`` is ``False, the
|
delete an existing test database. If ``interactive`` is ``False, the
|
||||||
test suite must be able to run without any manual intervention.
|
test suite must be able to run without any manual intervention.
|
||||||
|
|
||||||
``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 ``module_list``.
|
||||||
|
|
||||||
This method should return the number of tests that failed.
|
This method should return the number of tests that failed.
|
||||||
|
|
||||||
Testing utilities
|
Testing utilities
|
||||||
|
|
|
@ -73,7 +73,7 @@ class InvalidModelTestCase(unittest.TestCase):
|
||||||
self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected))
|
self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected))
|
||||||
self.assert_(not missing, "Missing Errors: " + '\n'.join(missing))
|
self.assert_(not missing, "Missing Errors: " + '\n'.join(missing))
|
||||||
|
|
||||||
def django_tests(verbosity, interactive, tests_to_run):
|
def django_tests(verbosity, interactive, test_labels):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
old_installed_apps = settings.INSTALLED_APPS
|
old_installed_apps = settings.INSTALLED_APPS
|
||||||
|
@ -109,14 +109,13 @@ def django_tests(verbosity, interactive, tests_to_run):
|
||||||
# if the model was named on the command line, or
|
# if the model was named on the command line, or
|
||||||
# no models were named (i.e., run all), import
|
# no models were named (i.e., run all), import
|
||||||
# this model and add it to the list to test.
|
# this model and add it to the list to test.
|
||||||
if not tests_to_run or model_name in tests_to_run:
|
if not test_labels or model_name in set([label.split('.')[0] for label in test_labels]):
|
||||||
if verbosity >= 1:
|
if verbosity >= 1:
|
||||||
print "Importing model %s" % model_name
|
print "Importing model %s" % model_name
|
||||||
mod = load_app(model_label)
|
mod = load_app(model_label)
|
||||||
if mod:
|
if mod:
|
||||||
if model_label not in settings.INSTALLED_APPS:
|
if model_label not in settings.INSTALLED_APPS:
|
||||||
settings.INSTALLED_APPS.append(model_label)
|
settings.INSTALLED_APPS.append(model_label)
|
||||||
test_models.append(mod)
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
|
sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
|
||||||
continue
|
continue
|
||||||
|
@ -125,12 +124,12 @@ def django_tests(verbosity, interactive, tests_to_run):
|
||||||
extra_tests = []
|
extra_tests = []
|
||||||
for model_dir, model_name in get_invalid_models():
|
for model_dir, model_name in get_invalid_models():
|
||||||
model_label = '.'.join([model_dir, model_name])
|
model_label = '.'.join([model_dir, model_name])
|
||||||
if not tests_to_run or model_name in tests_to_run:
|
if not test_labels or model_name in test_labels:
|
||||||
extra_tests.append(InvalidModelTestCase(model_label))
|
extra_tests.append(InvalidModelTestCase(model_label))
|
||||||
|
|
||||||
# Run the test suite, including the extra validation tests.
|
# Run the test suite, including the extra validation tests.
|
||||||
from django.test.simple import run_tests
|
from django.test.simple import run_tests
|
||||||
failures = run_tests(test_models, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests)
|
failures = run_tests(test_labels, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests)
|
||||||
if failures:
|
if failures:
|
||||||
sys.exit(failures)
|
sys.exit(failures)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue