|
1 import unittest |
|
2 from django.conf import settings |
|
3 from django.db.models import get_app, get_apps |
|
4 from django.test import _doctest as doctest |
|
5 from django.test.utils import setup_test_environment, teardown_test_environment |
|
6 from django.test.testcases import OutputChecker, DocTestRunner, TestCase |
|
7 |
|
8 # The module name for tests outside models.py |
|
9 TEST_MODULE = 'tests' |
|
10 |
|
11 doctestOutputChecker = OutputChecker() |
|
12 |
|
13 def get_tests(app_module): |
|
14 try: |
|
15 app_path = app_module.__name__.split('.')[:-1] |
|
16 test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE) |
|
17 except ImportError, e: |
|
18 # Couldn't import tests.py. Was it due to a missing file, or |
|
19 # due to an import error in a tests.py that actually exists? |
|
20 import os.path |
|
21 from imp import find_module |
|
22 try: |
|
23 mod = find_module(TEST_MODULE, [os.path.dirname(app_module.__file__)]) |
|
24 except ImportError: |
|
25 # 'tests' module doesn't exist. Move on. |
|
26 test_module = None |
|
27 else: |
|
28 # The module exists, so there must be an import error in the |
|
29 # test module itself. We don't need the module; so if the |
|
30 # module was a single file module (i.e., tests.py), close the file |
|
31 # handle returned by find_module. Otherwise, the test module |
|
32 # is a directory, and there is nothing to close. |
|
33 if mod[0]: |
|
34 mod[0].close() |
|
35 raise |
|
36 return test_module |
|
37 |
|
38 def build_suite(app_module): |
|
39 "Create a complete Django test suite for the provided application module" |
|
40 suite = unittest.TestSuite() |
|
41 |
|
42 # Load unit and doctests in the models.py module. If module has |
|
43 # a suite() method, use it. Otherwise build the test suite ourselves. |
|
44 if hasattr(app_module, 'suite'): |
|
45 suite.addTest(app_module.suite()) |
|
46 else: |
|
47 suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(app_module)) |
|
48 try: |
|
49 suite.addTest(doctest.DocTestSuite(app_module, |
|
50 checker=doctestOutputChecker, |
|
51 runner=DocTestRunner)) |
|
52 except ValueError: |
|
53 # No doc tests in models.py |
|
54 pass |
|
55 |
|
56 # Check to see if a separate 'tests' module exists parallel to the |
|
57 # models module |
|
58 test_module = get_tests(app_module) |
|
59 if test_module: |
|
60 # Load unit and doctests in the tests.py module. If module has |
|
61 # a suite() method, use it. Otherwise build the test suite ourselves. |
|
62 if hasattr(test_module, 'suite'): |
|
63 suite.addTest(test_module.suite()) |
|
64 else: |
|
65 suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_module)) |
|
66 try: |
|
67 suite.addTest(doctest.DocTestSuite(test_module, |
|
68 checker=doctestOutputChecker, |
|
69 runner=DocTestRunner)) |
|
70 except ValueError: |
|
71 # No doc tests in tests.py |
|
72 pass |
|
73 return suite |
|
74 |
|
75 def build_test(label): |
|
76 """Construct a test case a test with the specified label. Label should |
|
77 be of the form model.TestClass or model.TestClass.test_method. Returns |
|
78 an instantiated test or test suite corresponding to the label provided. |
|
79 |
|
80 """ |
|
81 parts = label.split('.') |
|
82 if len(parts) < 2 or len(parts) > 3: |
|
83 raise ValueError("Test label '%s' should be of the form app.TestCase or app.TestCase.test_method" % label) |
|
84 |
|
85 app_module = get_app(parts[0]) |
|
86 TestClass = getattr(app_module, parts[1], None) |
|
87 |
|
88 # Couldn't find the test class in models.py; look in tests.py |
|
89 if TestClass is None: |
|
90 test_module = get_tests(app_module) |
|
91 if test_module: |
|
92 TestClass = getattr(test_module, parts[1], None) |
|
93 |
|
94 if len(parts) == 2: # label is app.TestClass |
|
95 try: |
|
96 return unittest.TestLoader().loadTestsFromTestCase(TestClass) |
|
97 except TypeError: |
|
98 raise ValueError("Test label '%s' does not refer to a test class" % label) |
|
99 else: # label is app.TestClass.test_method |
|
100 if not TestClass: |
|
101 raise ValueError("Test label '%s' does not refer to a test class" % label) |
|
102 return TestClass(parts[2]) |
|
103 |
|
104 # Python 2.3 compatibility: TestSuites were made iterable in 2.4. |
|
105 # We need to iterate over them, so we add the missing method when |
|
106 # necessary. |
|
107 try: |
|
108 getattr(unittest.TestSuite, '__iter__') |
|
109 except AttributeError: |
|
110 setattr(unittest.TestSuite, '__iter__', lambda s: iter(s._tests)) |
|
111 |
|
112 def partition_suite(suite, classes, bins): |
|
113 """ |
|
114 Partitions a test suite by test type. |
|
115 |
|
116 classes is a sequence of types |
|
117 bins is a sequence of TestSuites, one more than classes |
|
118 |
|
119 Tests of type classes[i] are added to bins[i], |
|
120 tests with no match found in classes are place in bins[-1] |
|
121 """ |
|
122 for test in suite: |
|
123 if isinstance(test, unittest.TestSuite): |
|
124 partition_suite(test, classes, bins) |
|
125 else: |
|
126 for i in range(len(classes)): |
|
127 if isinstance(test, classes[i]): |
|
128 bins[i].addTest(test) |
|
129 break |
|
130 else: |
|
131 bins[-1].addTest(test) |
|
132 |
|
133 def reorder_suite(suite, classes): |
|
134 """ |
|
135 Reorders a test suite by test type. |
|
136 |
|
137 classes is a sequence of types |
|
138 |
|
139 All tests of type clases[0] are placed first, then tests of type classes[1], etc. |
|
140 Tests with no match in classes are placed last. |
|
141 """ |
|
142 class_count = len(classes) |
|
143 bins = [unittest.TestSuite() for i in range(class_count+1)] |
|
144 partition_suite(suite, classes, bins) |
|
145 for i in range(class_count): |
|
146 bins[0].addTests(bins[i+1]) |
|
147 return bins[0] |
|
148 |
|
149 def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]): |
|
150 """ |
|
151 Run the unit tests for all the test labels in the provided list. |
|
152 Labels must be of the form: |
|
153 - app.TestClass.test_method |
|
154 Run a single specific test method |
|
155 - app.TestClass |
|
156 Run all the test methods in a given class |
|
157 - app |
|
158 Search for doctests and unittests in the named application. |
|
159 |
|
160 When looking for tests, the test runner will look in the models and |
|
161 tests modules for the application. |
|
162 |
|
163 A list of 'extra' tests may also be provided; these tests |
|
164 will be added to the test suite. |
|
165 |
|
166 Returns the number of tests that failed. |
|
167 """ |
|
168 setup_test_environment() |
|
169 |
|
170 settings.DEBUG = False |
|
171 suite = unittest.TestSuite() |
|
172 |
|
173 if test_labels: |
|
174 for label in test_labels: |
|
175 if '.' in label: |
|
176 suite.addTest(build_test(label)) |
|
177 else: |
|
178 app = get_app(label) |
|
179 suite.addTest(build_suite(app)) |
|
180 else: |
|
181 for app in get_apps(): |
|
182 suite.addTest(build_suite(app)) |
|
183 |
|
184 for test in extra_tests: |
|
185 suite.addTest(test) |
|
186 |
|
187 suite = reorder_suite(suite, (TestCase,)) |
|
188 |
|
189 old_name = settings.DATABASE_NAME |
|
190 from django.db import connection |
|
191 connection.creation.create_test_db(verbosity, autoclobber=not interactive) |
|
192 result = unittest.TextTestRunner(verbosity=verbosity).run(suite) |
|
193 connection.creation.destroy_test_db(old_name, verbosity) |
|
194 |
|
195 teardown_test_environment() |
|
196 |
|
197 return len(result.failures) + len(result.errors) |