Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #4460 -- Added the ability to be more specific in the test case…

…s 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
  • Loading branch information...
commit 650cea9170313d9a2197d4050f6dcabaaeaa3b20 1 parent 5b8d2c9
@freakboy3742 freakboy3742 authored
View
11 django/core/management.py
@@ -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)
View
112 django/test/simple.py
@@ -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 @@
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.
+
+ """
+ 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 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
+ 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)
View
44 docs/testing.txt
@@ -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
View
9 tests/runtests.py
@@ -73,7 +73,7 @@ def runTest(self):
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)
Please sign in to comment.
Something went wrong with that request. Please try again.