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)
|
||||
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"
|
||||
from django.conf import settings
|
||||
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('.')
|
||||
# Allow for Python 2.5 relative paths
|
||||
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_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:
|
||||
sys.exit(failures)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import unittest
|
||||
from django.conf import settings
|
||||
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.utils import create_test_db, destroy_test_db
|
||||
|
@ -10,6 +11,31 @@ TEST_MODULE = 'tests'
|
|||
|
||||
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):
|
||||
"Create a complete Django test suite for the provided application module"
|
||||
suite = unittest.TestSuite()
|
||||
|
@ -30,10 +56,8 @@ def build_suite(app_module):
|
|||
|
||||
# Check to see if a separate 'tests' module exists parallel to the
|
||||
# models module
|
||||
try:
|
||||
app_path = app_module.__name__.split('.')[:-1]
|
||||
test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE)
|
||||
|
||||
test_module = get_tests(app_module)
|
||||
if test_module:
|
||||
# Load unit and doctests in the tests.py module. If module has
|
||||
# a suite() method, use it. Otherwise build the test suite ourselves.
|
||||
if hasattr(test_module, 'suite'):
|
||||
|
@ -47,34 +71,50 @@ def build_suite(app_module):
|
|||
except ValueError:
|
||||
# No doc tests in tests.py
|
||||
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
|
||||
|
||||
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.
|
||||
This testrunner will search each of the modules in the provided list,
|
||||
looking for doctests and unittests in models.py or tests.py within
|
||||
the module. A list of 'extra' tests may also be provided; these tests
|
||||
parts = label.split('.')
|
||||
if len(parts) < 2 or len(parts) > 3:
|
||||
raise ValueError("Test label '%s' should be of the form app.TestCase or app.TestCase.test_method" % label)
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
for module in module_list:
|
||||
suite.addTest(build_suite(module))
|
||||
|
||||
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))
|
||||
|
||||
for test in extra_tests:
|
||||
suite.addTest(test)
|
||||
|
|
|
@ -450,6 +450,9 @@ look like::
|
|||
def setUp(self):
|
||||
# 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
|
||||
flush the database, returning the database the state it was in directly
|
||||
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)``
|
||||
Assert that a response indicates that a page could be retrieved and
|
||||
produced the nominated status code, and that ``text`` in the content
|
||||
of the response. If ``count`` is provided, ``text`` must occur exactly
|
||||
produced the nominated status code, and that ``text`` in the content
|
||||
of the response. If ``count`` is provided, ``text`` must occur exactly
|
||||
``count`` times in the response.
|
||||
|
||||
``assertFormError(response, form, field, errors)``
|
||||
|
@ -571,6 +574,18 @@ but you only want to run the animals unit tests, run::
|
|||
|
||||
$ ./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
|
||||
database is created and models are initialized. This test database is
|
||||
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
|
||||
same arguments as the Django test runner:
|
||||
|
||||
``run_tests(module_list, verbosity=1, interactive=True, extra_tests=[])``
|
||||
The module list is the list of Python modules that contain the models to be
|
||||
tested. This is the same format returned by ``django.db.models.get_apps()``.
|
||||
The test runner should search these modules for tests to execute.
|
||||
``run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[])``
|
||||
**New in Django development version:** ``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 ``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.
|
||||
|
||||
**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
|
||||
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.
|
||||
|
||||
``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
|
||||
|
||||
``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``.
|
||||
|
||||
|
||||
This method should return the number of tests that failed.
|
||||
|
||||
Testing utilities
|
||||
|
|
|
@ -73,7 +73,7 @@ class InvalidModelTestCase(unittest.TestCase):
|
|||
self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected))
|
||||
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
|
||||
|
||||
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
|
||||
# no models were named (i.e., run all), import
|
||||
# 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:
|
||||
print "Importing model %s" % model_name
|
||||
mod = load_app(model_label)
|
||||
if mod:
|
||||
if model_label not in settings.INSTALLED_APPS:
|
||||
settings.INSTALLED_APPS.append(model_label)
|
||||
test_models.append(mod)
|
||||
except Exception, e:
|
||||
sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
|
||||
continue
|
||||
|
@ -125,12 +124,12 @@ def django_tests(verbosity, interactive, tests_to_run):
|
|||
extra_tests = []
|
||||
for model_dir, model_name in get_invalid_models():
|
||||
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))
|
||||
|
||||
# Run the test suite, including the extra validation 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:
|
||||
sys.exit(failures)
|
||||
|
||||
|
|
Loading…
Reference in New Issue