|
0
|
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) |