Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
Russell Keith-Magee authored
11  django/core/management.py
@@ -1331,16 +1331,11 @@ def runfcgi(args):
1331 1331
     runfastcgi(args)
1332 1332
 runfcgi.args = '[various KEY=val options, use `runfcgi help` for help]'
1333 1333
 
1334  
-def test(app_labels, verbosity=1, interactive=True):
  1334
+def test(test_labels, verbosity=1, interactive=True):
1335 1335
     "Runs the test suite for the specified applications"
1336 1336
     from django.conf import settings
1337 1337
     from django.db.models import get_app, get_apps
1338  
-
1339  
-    if len(app_labels) == 0:
1340  
-        app_list = get_apps()
1341  
-    else:
1342  
-        app_list = [get_app(app_label) for app_label in app_labels]
1343  
-
  1338
+    
1344 1339
     test_path = settings.TEST_RUNNER.split('.')
1345 1340
     # Allow for Python 2.5 relative paths
1346 1341
     if len(test_path) > 1:
@@ -1350,7 +1345,7 @@ def test(app_labels, verbosity=1, interactive=True):
1350 1345
     test_module = __import__(test_module_name, {}, {}, test_path[-1])
1351 1346
     test_runner = getattr(test_module, test_path[-1])
1352 1347
 
1353  
-    failures = test_runner(app_list, verbosity=verbosity, interactive=interactive)
  1348
+    failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
1354 1349
     if failures:
1355 1350
         sys.exit(failures)
1356 1351
 
112  django/test/simple.py
... ...
@@ -1,5 +1,6 @@
1 1
 import unittest
2 2
 from django.conf import settings
  3
+from django.db.models import get_app, get_apps
3 4
 from django.test import _doctest as doctest
4 5
 from django.test.utils import setup_test_environment, teardown_test_environment
5 6
 from django.test.utils import create_test_db, destroy_test_db
@@ -10,6 +11,31 @@
10 11
     
11 12
 doctestOutputChecker = OutputChecker()
12 13
 
  14
+def get_tests(app_module):
  15
+    try:
  16
+        app_path = app_module.__name__.split('.')[:-1]
  17
+        test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE)
  18
+    except ImportError, e:
  19
+        # Couldn't import tests.py. Was it due to a missing file, or
  20
+        # due to an import error in a tests.py that actually exists?
  21
+        import os.path
  22
+        from imp import find_module
  23
+        try:
  24
+            mod = find_module(TEST_MODULE, [os.path.dirname(app_module.__file__)])
  25
+        except ImportError:
  26
+            # 'tests' module doesn't exist. Move on.
  27
+            test_module = None
  28
+        else:
  29
+            # The module exists, so there must be an import error in the 
  30
+            # test module itself. We don't need the module; so if the
  31
+            # module was a single file module (i.e., tests.py), close the file
  32
+            # handle returned by find_module. Otherwise, the test module
  33
+            # is a directory, and there is nothing to close.
  34
+            if mod[0]:
  35
+                mod[0].close()
  36
+            raise
  37
+    return test_module
  38
+    
13 39
 def build_suite(app_module):
14 40
     "Create a complete Django test suite for the provided application module"
15 41
     suite = unittest.TestSuite()
@@ -30,10 +56,8 @@ def build_suite(app_module):
30 56
     
31 57
     # Check to see if a separate 'tests' module exists parallel to the 
32 58
     # models module
33  
-    try:
34  
-        app_path = app_module.__name__.split('.')[:-1]
35  
-        test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE)
36  
-        
  59
+    test_module = get_tests(app_module)
  60
+    if test_module:
37 61
         # Load unit and doctests in the tests.py module. If module has
38 62
         # a suite() method, use it. Otherwise build the test suite ourselves.
39 63
         if hasattr(test_module, 'suite'):
@@ -47,34 +71,50 @@ def build_suite(app_module):
47 71
             except ValueError:
48 72
                 # No doc tests in tests.py
49 73
                 pass
50  
-    except ImportError, e:
51  
-        # Couldn't import tests.py. Was it due to a missing file, or
52  
-        # due to an import error in a tests.py that actually exists?
53  
-        import os.path
54  
-        from imp import find_module
55  
-        try:
56  
-            mod = find_module(TEST_MODULE, [os.path.dirname(app_module.__file__)])
57  
-        except ImportError:
58  
-            # 'tests' module doesn't exist. Move on.
59  
-            pass
60  
-        else:
61  
-            # The module exists, so there must be an import error in the 
62  
-            # test module itself. We don't need the module; so if the
63  
-            # module was a single file module (i.e., tests.py), close the file
64  
-            # handle returned by find_module. Otherwise, the test module
65  
-            # is a directory, and there is nothing to close.
66  
-            if mod[0]:
67  
-                mod[0].close()
68  
-            raise
69  
-            
70 74
     return suite
71 75
 
72  
-def run_tests(module_list, verbosity=1, interactive=True, extra_tests=[]):
  76
+def build_test(label):
  77
+    """Construct a test case a test with the specified label. Label should 
  78
+    be of the form model.TestClass or model.TestClass.test_method. Returns
  79
+    an instantiated test or test suite corresponding to the label provided.
  80
+        
  81
+    """
  82
+    parts = label.split('.')
  83
+    if len(parts) < 2 or len(parts) > 3:
  84
+        raise ValueError("Test label '%s' should be of the form app.TestCase or app.TestCase.test_method" % label)
  85
+    
  86
+    app_module = get_app(parts[0])
  87
+    TestClass = getattr(app_module, parts[1], None)
  88
+
  89
+    # Couldn't find the test class in models.py; look in tests.py
  90
+    if TestClass is None:
  91
+        test_module = get_tests(app_module)
  92
+        if test_module:
  93
+            TestClass = getattr(test_module, parts[1], None)
  94
+
  95
+    if len(parts) == 2: # label is app.TestClass
  96
+        try:
  97
+            return unittest.TestLoader().loadTestsFromTestCase(TestClass)
  98
+        except TypeError:
  99
+            raise ValueError("Test label '%s' does not refer to a test class" % label)            
  100
+    else: # label is app.TestClass.test_method
  101
+        return TestClass(parts[2])
  102
+
  103
+def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):
73 104
     """
74  
-    Run the unit tests for all the modules in the provided list.
75  
-    This testrunner will search each of the modules in the provided list,
76  
-    looking for doctests and unittests in models.py or tests.py within
77  
-    the module. A list of 'extra' tests may also be provided; these tests
  105
+    Run the unit tests for all the test labels in the provided list.
  106
+    Labels must be of the form:
  107
+     - app.TestClass.test_method
  108
+        Run a single specific test method
  109
+     - app.TestClass
  110
+        Run all the test methods in a given class
  111
+     - app
  112
+        Search for doctests and unittests in the named application.
  113
+
  114
+    When looking for tests, the test runner will look in the models and
  115
+    tests modules for the application.
  116
+    
  117
+    A list of 'extra' tests may also be provided; these tests
78 118
     will be added to the test suite.
79 119
     
80 120
     Returns the number of tests that failed.
@@ -83,9 +123,17 @@ def run_tests(module_list, verbosity=1, interactive=True, extra_tests=[]):
83 123
     
84 124
     settings.DEBUG = False    
85 125
     suite = unittest.TestSuite()
86  
-     
87  
-    for module in module_list:
88  
-        suite.addTest(build_suite(module))
  126
+    
  127
+    if test_labels:
  128
+        for label in test_labels:
  129
+            if '.' in label:
  130
+                suite.addTest(build_test(label))
  131
+            else:
  132
+                app = get_app(label)
  133
+                suite.addTest(build_suite(app))
  134
+    else:
  135
+        for app in get_apps():
  136
+            suite.addTest(build_suite(app))
89 137
     
90 138
     for test in extra_tests:
91 139
         suite.addTest(test)
44  docs/testing.txt
@@ -450,6 +450,9 @@ look like::
450 450
         def setUp(self):
451 451
             # test definitions as before
452 452
 
  453
+        def testFluffyAnimals(self):
  454
+            # A test that uses the fixtures
  455
+
453 456
 At the start of each test case, before ``setUp()`` is run, Django will
454 457
 flush the database, returning the database the state it was in directly
455 458
 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.
483 486
 
484 487
 ``assertContains(response, text, count=None, status_code=200)``
485 488
     Assert that a response indicates that a page could be retrieved and
486  
-    produced the nominated status code, and that ``text`` in the content 
487  
-    of the response. If ``count`` is provided, ``text`` must occur exactly 
  489
+    produced the nominated status code, and that ``text`` in the content
  490
+    of the response. If ``count`` is provided, ``text`` must occur exactly
488 491
     ``count`` times in the response.
489 492
 
490 493
 ``assertFormError(response, form, field, errors)``
@@ -571,6 +574,18 @@ but you only want to run the animals unit tests, run::
571 574
 
572 575
     $ ./manage.py test animals
573 576
 
  577
+**New in Django development version:** If you use unit tests, you can be more
  578
+specific in the tests that are executed. To run a single test case in an
  579
+application (for example, the AnimalTestCase described previously), add the
  580
+name of the test case to the label on the command line::
  581
+
  582
+    $ ./manage.py test animals.AnimalTestCase
  583
+
  584
+**New in Django development version:**To run a single test method inside a
  585
+test case, add the name of the test method to the label::
  586
+
  587
+    $ ./manage.py test animals.AnimalTestCase.testFluffyAnimals
  588
+
574 589
 When you run your tests, you'll see a bunch of text flow by as the test
575 590
 database is created and models are initialized. This test database is
576 591
 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
665 680
 can call it anything you want. The only requirement is that it has the
666 681
 same arguments as the Django test runner:
667 682
 
668  
-``run_tests(module_list, verbosity=1, interactive=True, extra_tests=[])``
669  
-    The module list is the list of Python modules that contain the models to be
670  
-    tested. This is the same format returned by ``django.db.models.get_apps()``.
671  
-    The test runner should search these modules for tests to execute.
  683
+``run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[])``
  684
+    **New in Django development version:** ``test_labels`` is a list of
  685
+    strings describing the tests to be run. A test label can take one of
  686
+    three forms:
  687
+        * ``app.TestCase.test_method`` - Run a single test method in a test case
  688
+        * ``app.TestCase`` - Run all the test methods in a test case
  689
+        * ``app`` - Search for and run all tests in the named application.
  690
+    If ``test_labels`` has a value of ``None``, the test runner should run
  691
+    search for tests in all the applications in ``INSTALLED_APPS``.
672 692
 
673 693
     Verbosity determines the amount of notification and debug information that
674 694
     will be printed to the console; ``0`` is no output, ``1`` is normal output,
675 695
     and ``2`` is verbose output.
676 696
 
677  
-    **New in Django development version** If ``interactive`` is ``True``, the
  697
+    **New in Django development version:** If ``interactive`` is ``True``, the
678 698
     test suite may ask the user for instructions when the test suite is
679 699
     executed. An example of this behavior would be asking for permission to
680  
-    delete an existing test database. If ``interactive`` is ``False, the 
  700
+    delete an existing test database. If ``interactive`` is ``False, the
681 701
     test suite must be able to run without any manual intervention.
682  
-    
683  
-    ``extra_tests`` is a list of extra ``TestCase`` instances to add to the 
684  
-    suite that is executed by the test runner. These extra tests are run 
  702
+
  703
+    ``extra_tests`` is a list of extra ``TestCase`` instances to add to the
  704
+    suite that is executed by the test runner. These extra tests are run
685 705
     in addition to those discovered in the modules listed in ``module_list``.
686  
-    
  706
+
687 707
     This method should return the number of tests that failed.
688 708
 
689 709
 Testing utilities
9  tests/runtests.py
@@ -73,7 +73,7 @@ def runTest(self):
73 73
         self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected))
74 74
         self.assert_(not missing, "Missing Errors: " + '\n'.join(missing))
75 75
 
76  
-def django_tests(verbosity, interactive, tests_to_run):
  76
+def django_tests(verbosity, interactive, test_labels):
77 77
     from django.conf import settings
78 78
 
79 79
     old_installed_apps = settings.INSTALLED_APPS
@@ -109,14 +109,13 @@ def django_tests(verbosity, interactive, tests_to_run):
109 109
             # if the model was named on the command line, or
110 110
             # no models were named (i.e., run all), import
111 111
             # this model and add it to the list to test.
112  
-            if not tests_to_run or model_name in tests_to_run:
  112
+            if not test_labels or model_name in set([label.split('.')[0] for label in test_labels]):
113 113
                 if verbosity >= 1:
114 114
                     print "Importing model %s" % model_name
115 115
                 mod = load_app(model_label)
116 116
                 if mod:
117 117
                     if model_label not in settings.INSTALLED_APPS:
118 118
                         settings.INSTALLED_APPS.append(model_label)
119  
-                    test_models.append(mod)
120 119
         except Exception, e:
121 120
             sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
122 121
             continue
@@ -125,12 +124,12 @@ def django_tests(verbosity, interactive, tests_to_run):
125 124
     extra_tests = []
126 125
     for model_dir, model_name in get_invalid_models():
127 126
         model_label = '.'.join([model_dir, model_name])
128  
-        if not tests_to_run or model_name in tests_to_run:
  127
+        if not test_labels or model_name in test_labels:
129 128
             extra_tests.append(InvalidModelTestCase(model_label))
130 129
 
131 130
     # Run the test suite, including the extra validation tests.
132 131
     from django.test.simple import run_tests
133  
-    failures = run_tests(test_models, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests)
  132
+    failures = run_tests(test_labels, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests)
134 133
     if failures:
135 134
         sys.exit(failures)
136 135
 

0 notes on commit 650cea9

Please sign in to comment.
Something went wrong with that request. Please try again.