Index: django/test/simple.py
===================================================================
--- django/test/simple.py	(revision 5754)
+++ django/test/simple.py	(working copy)
@@ -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 @@
     
     # 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 @@
             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,10 +123,18 @@
     
     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)
 
Index: django/core/management.py
===================================================================
--- django/core/management.py	(revision 5754)
+++ django/core/management.py	(working copy)
@@ -1331,16 +1331,11 @@
     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 @@
     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)
 
Index: tests/runtests.py
===================================================================
--- tests/runtests.py	(revision 5754)
+++ tests/runtests.py	(working copy)
@@ -73,7 +73,7 @@
         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 @@
             # 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 @@
     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)
 
Index: docs/testing.txt
===================================================================
--- docs/testing.txt	(revision 5754)
+++ docs/testing.txt	(working copy)
@@ -450,6 +450,9 @@
         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 @@
 
 ``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 @@
 
     $ ./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 @@
 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
