1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
|
import sys, time, traceback, 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.testcases import OutputChecker, DocTestRunner, TestCase
# The module name for tests outside models.py
TEST_MODULE = 'tests'
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()
# Load unit and doctests in the models.py module. If module has
# a suite() method, use it. Otherwise build the test suite ourselves.
if hasattr(app_module, 'suite'):
suite.addTest(app_module.suite())
else:
suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(app_module))
try:
suite.addTest(doctest.DocTestSuite(app_module,
checker=doctestOutputChecker,
runner=DocTestRunner))
except ValueError:
# No doc tests in models.py
pass
# Check to see if a separate 'tests' module exists parallel to the
# models 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'):
suite.addTest(test_module.suite())
else:
suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_module))
try:
suite.addTest(doctest.DocTestSuite(test_module,
checker=doctestOutputChecker,
runner=DocTestRunner))
except ValueError:
# No doc tests in tests.py
pass
return suite
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
if not TestClass:
raise ValueError("Test label '%s' does not refer to a test class" % label)
return TestClass(parts[2])
# Python 2.3 compatibility: TestSuites were made iterable in 2.4.
# We need to iterate over them, so we add the missing method when
# necessary.
try:
getattr(unittest.TestSuite, '__iter__')
except AttributeError:
setattr(unittest.TestSuite, '__iter__', lambda s: iter(s._tests))
def partition_suite(suite, classes, bins):
"""
Partitions a test suite by test type.
classes is a sequence of types
bins is a sequence of TestSuites, one more than classes
Tests of type classes[i] are added to bins[i],
tests with no match found in classes are place in bins[-1]
"""
for test in suite:
if isinstance(test, unittest.TestSuite):
partition_suite(test, classes, bins)
else:
for i in range(len(classes)):
if isinstance(test, classes[i]):
bins[i].addTest(test)
break
else:
bins[-1].addTest(test)
def reorder_suite(suite, classes):
"""
Reorders a test suite by test type.
classes is a sequence of types
All tests of type clases[0] are placed first, then tests of type classes[1], etc.
Tests with no match in classes are placed last.
"""
class_count = len(classes)
bins = [unittest.TestSuite() for i in range(class_count+1)]
partition_suite(suite, classes, bins)
for i in range(class_count):
bins[0].addTests(bins[i+1])
return bins[0]
class DefaultTestRunner(object):
"""
The original test runner. No coverage reporting.
"""
def __init__(self):
"""
Placeholder constructor. Want to make it obvious that it can
be overridden.
"""
self.isloaded = True
def run_tests(self, 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.
"""
setup_test_environment()
settings.DEBUG = False
suite = unittest.TestSuite()
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)
suite = reorder_suite(suite, (TestCase,))
old_name = settings.DATABASE_NAME
from django.db import connection
connection.creation.create_test_db(verbosity, autoclobber=not interactive)
result = SkipTestRunner(verbosity=verbosity).run(suite)
connection.creation.destroy_test_db(old_name, verbosity)
teardown_test_environment()
return len(result.failures) + len(result.errors)
class SkipTestRunner:
"""
A test runner class that adds a Skipped category in the output layer.
Modeled after unittest.TextTestRunner.
Similarly to unittest.TextTestRunner, prints summary of the results at the end.
(Including a count of skipped tests.)
"""
def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1):
self.stream = unittest._WritelnDecorator(stream)
self.descriptions = descriptions
self.verbosity = verbosity
self.result = _SkipTestResult(self.stream, descriptions, verbosity)
def run(self, test):
"Run the given test case or test suite."
startTime = time.time()
test.run(self.result)
stopTime = time.time()
timeTaken = stopTime - startTime
self.result.printErrors()
self.stream.writeln(self.result.separator2)
run = self.result.testsRun
self.stream.writeln('Ran %d test%s in %.3fs' %
(run, run != 1 and 's' or '', timeTaken))
self.stream.writeln()
if not self.result.wasSuccessful():
self.stream.write('FAILED (')
failed, errored, skipped = map(len, (self.result.failures, self.result.errors, self.result.skipped))
if failed:
self.stream.write('failures=%d' % failed)
if errored:
if failed: self.stream.write(', ')
self.stream.write('errors=%d' % errored)
if skipped:
if errored or failed: self.stream.write(', ')
self.stream.write('skipped=%d' % skipped)
self.stream.writeln(')')
else:
self.stream.writeln('OK')
return self.result
class _SkipTestResult(unittest._TextTestResult):
"""
A test result class that adds a Skipped category in the output layer.
Modeled after unittest._TextTestResult.
Similarly to unittest._TextTestResult, prints out the names of tests as they are
run and errors as they occur.
"""
def __init__(self, stream, descriptions, verbosity):
unittest._TextTestResult.__init__(self, stream, descriptions, verbosity)
self.skipped = []
def addError(self, test, err):
# Determine if this is a skipped test
tracebacks = traceback.extract_tb(err[2])
if tracebacks[-1][-1].startswith('raise SkippedTest'):
self.skipped.append((test, self._exc_info_to_string(err, test)))
if self.showAll:
self.stream.writeln('SKIPPED')
elif self.dots:
self.stream.write('S')
self.stream.flush()
else:
unittest.TestResult.addError(self, test, err)
if self.showAll:
self.stream.writeln('ERROR')
elif self.dots:
self.stream.write('E')
self.stream.flush()
def printErrors(self):
if self.dots or self.showAll:
self.stream.writeln()
self.printErrorList('SKIPPED', self.skipped)
self.printErrorList('ERROR', self.errors)
self.printErrorList('FAIL', self.failures)
|