blob: b3eec181e36244b73b5e8cf2b2e29153bc288c20 [file] [log] [blame]
Fred Drake02538202001-03-21 18:09:46 +00001#!/usr/bin/env python
2"""
3Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's
4Smalltalk testing framework.
5
6Further information is available in the bundled documentation, and from
7
8 http://pyunit.sourceforge.net/
9
10This module contains the core framework classes that form the basis of
11specific test cases and suites (TestCase, TestSuite etc.), and also a
12text-based utility class for running the tests and reporting the results
13(TextTestRunner).
14
15Copyright (c) 1999, 2000, 2001 Steve Purcell
16This module is free software, and you may redistribute it and/or modify
17it under the same terms as Python itself, so long as this copyright message
18and disclaimer are retained in their original form.
19
20IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
21SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
22THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
23DAMAGE.
24
25THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
26LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
27PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
28AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
29SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
30"""
31
32__author__ = "Steve Purcell (stephen_purcell@yahoo.com)"
33__version__ = "$Revision$"[11:-2]
34
35import time
36import sys
37import traceback
38import string
39import os
40
41##############################################################################
42# A platform-specific concession to help the code work for JPython users
43##############################################################################
44
45plat = string.lower(sys.platform)
46_isJPython = string.find(plat, 'java') >= 0 or string.find(plat, 'jdk') >= 0
47del plat
48
49
50##############################################################################
51# Test framework core
52##############################################################################
53
54class TestResult:
55 """Holder for test result information.
56
57 Test results are automatically managed by the TestCase and TestSuite
58 classes, and do not need to be explicitly manipulated by writers of tests.
59
60 Each instance holds the total number of tests run, and collections of
61 failures and errors that occurred among those test runs. The collections
62 contain tuples of (testcase, exceptioninfo), where exceptioninfo is a
63 tuple of values as returned by sys.exc_info().
64 """
65 def __init__(self):
66 self.failures = []
67 self.errors = []
68 self.testsRun = 0
69 self.shouldStop = 0
70
71 def startTest(self, test):
72 "Called when the given test is about to be run"
73 self.testsRun = self.testsRun + 1
74
75 def stopTest(self, test):
76 "Called when the given test has been run"
77 pass
78
79 def addError(self, test, err):
80 "Called when an error has occurred"
81 self.errors.append((test, err))
82
83 def addFailure(self, test, err):
84 "Called when a failure has occurred"
85 self.failures.append((test, err))
86
87 def wasSuccessful(self):
88 "Tells whether or not this result was a success"
89 return len(self.failures) == len(self.errors) == 0
90
91 def stop(self):
92 "Indicates that the tests should be aborted"
93 self.shouldStop = 1
94
95 def __repr__(self):
96 return "<%s run=%i errors=%i failures=%i>" % \
97 (self.__class__, self.testsRun, len(self.errors),
98 len(self.failures))
99
100
101class TestCase:
102 """A class whose instances are single test cases.
103
104 Test authors should subclass TestCase for their own tests. Construction
105 and deconstruction of the test's environment ('fixture') can be
106 implemented by overriding the 'setUp' and 'tearDown' methods respectively.
107
108 By default, the test code itself should be placed in a method named
109 'runTest'.
110
111 If the fixture may be used for many test cases, create as
112 many test methods as are needed. When instantiating such a TestCase
113 subclass, specify in the constructor arguments the name of the test method
114 that the instance is to execute.
115 """
116 def __init__(self, methodName='runTest'):
117 """Create an instance of the class that will use the named test
118 method when executed. Raises a ValueError if the instance does
119 not have a method with the specified name.
120 """
121 try:
122 self.__testMethod = getattr(self,methodName)
123 except AttributeError:
124 raise ValueError, "no such test method in %s: %s" % \
125 (self.__class__, methodName)
126
127 def setUp(self):
128 "Hook method for setting up the test fixture before exercising it."
129 pass
130
131 def tearDown(self):
132 "Hook method for deconstructing the test fixture after testing it."
133 pass
134
135 def countTestCases(self):
136 return 1
137
138 def defaultTestResult(self):
139 return TestResult()
140
141 def shortDescription(self):
142 """Returns a one-line description of the test, or None if no
143 description has been provided.
144
145 The default implementation of this method returns the first line of
146 the specified test method's docstring.
147 """
148 doc = self.__testMethod.__doc__
149 return doc and string.strip(string.split(doc, "\n")[0]) or None
150
151 def id(self):
152 return "%s.%s" % (self.__class__, self.__testMethod.__name__)
153
154 def __str__(self):
155 return "%s (%s)" % (self.__testMethod.__name__, self.__class__)
156
157 def __repr__(self):
158 return "<%s testMethod=%s>" % \
159 (self.__class__, self.__testMethod.__name__)
160
161 def run(self, result=None):
162 return self(result)
163
164 def __call__(self, result=None):
165 if result is None: result = self.defaultTestResult()
166 result.startTest(self)
167 try:
168 try:
169 self.setUp()
170 except:
171 result.addError(self,self.__exc_info())
172 return
173
174 try:
175 self.__testMethod()
176 except AssertionError, e:
177 result.addFailure(self,self.__exc_info())
178 except:
179 result.addError(self,self.__exc_info())
180
181 try:
182 self.tearDown()
183 except:
184 result.addError(self,self.__exc_info())
185 finally:
186 result.stopTest(self)
187
188 def debug(self):
189 self.setUp()
190 self.__testMethod()
191 self.tearDown()
192
193 def assert_(self, expr, msg=None):
194 """Equivalent of built-in 'assert', but is not optimised out when
195 __debug__ is false.
196 """
197 if not expr:
198 raise AssertionError, msg
199
200 failUnless = assert_
201
202 def failIf(self, expr, msg=None):
203 "Fail the test if the expression is true."
204 apply(self.assert_,(not expr,msg))
205
206 def assertRaises(self, excClass, callableObj, *args, **kwargs):
207 """Assert that an exception of class excClass is thrown
208 by callableObj when invoked with arguments args and keyword
209 arguments kwargs. If a different type of exception is
210 thrown, it will not be caught, and the test case will be
211 deemed to have suffered an error, exactly as for an
212 unexpected exception.
213 """
214 try:
215 apply(callableObj, args, kwargs)
216 except excClass:
217 return
218 else:
219 if hasattr(excClass,'__name__'): excName = excClass.__name__
220 else: excName = str(excClass)
221 raise AssertionError, excName
222
223 def fail(self, msg=None):
224 """Fail immediately, with the given message."""
225 raise AssertionError, msg
226
227 def __exc_info(self):
228 """Return a version of sys.exc_info() with the traceback frame
229 minimised; usually the top level of the traceback frame is not
230 needed.
231 """
232 exctype, excvalue, tb = sys.exc_info()
233 newtb = tb.tb_next
234 if newtb is None:
235 return (exctype, excvalue, tb)
236 return (exctype, excvalue, newtb)
237
238
239class TestSuite:
240 """A test suite is a composite test consisting of a number of TestCases.
241
242 For use, create an instance of TestSuite, then add test case instances.
243 When all tests have been added, the suite can be passed to a test
244 runner, such as TextTestRunner. It will run the individual test cases
245 in the order in which they were added, aggregating the results. When
246 subclassing, do not forget to call the base class constructor.
247 """
248 def __init__(self, tests=()):
249 self._tests = []
250 self.addTests(tests)
251
252 def __repr__(self):
253 return "<%s tests=%s>" % (self.__class__, self._tests)
254
255 __str__ = __repr__
256
257 def countTestCases(self):
258 cases = 0
259 for test in self._tests:
260 cases = cases + test.countTestCases()
261 return cases
262
263 def addTest(self, test):
264 self._tests.append(test)
265
266 def addTests(self, tests):
267 for test in tests:
268 self.addTest(test)
269
270 def run(self, result):
271 return self(result)
272
273 def __call__(self, result):
274 for test in self._tests:
275 if result.shouldStop:
276 break
277 test(result)
278 return result
279
280 def debug(self):
281 for test in self._tests: test.debug()
282
283
284
285class FunctionTestCase(TestCase):
286 """A test case that wraps a test function.
287
288 This is useful for slipping pre-existing test functions into the
289 PyUnit framework. Optionally, set-up and tidy-up functions can be
290 supplied. As with TestCase, the tidy-up ('tearDown') function will
291 always be called if the set-up ('setUp') function ran successfully.
292 """
293
294 def __init__(self, testFunc, setUp=None, tearDown=None,
295 description=None):
296 TestCase.__init__(self)
297 self.__setUpFunc = setUp
298 self.__tearDownFunc = tearDown
299 self.__testFunc = testFunc
300 self.__description = description
301
302 def setUp(self):
303 if self.__setUpFunc is not None:
304 self.__setUpFunc()
305
306 def tearDown(self):
307 if self.__tearDownFunc is not None:
308 self.__tearDownFunc()
309
310 def runTest(self):
311 self.__testFunc()
312
313 def id(self):
314 return self.__testFunc.__name__
315
316 def __str__(self):
317 return "%s (%s)" % (self.__class__, self.__testFunc.__name__)
318
319 def __repr__(self):
320 return "<%s testFunc=%s>" % (self.__class__, self.__testFunc)
321
322 def shortDescription(self):
323 if self.__description is not None: return self.__description
324 doc = self.__testFunc.__doc__
325 return doc and string.strip(string.split(doc, "\n")[0]) or None
326
327
328
329##############################################################################
330# Convenience functions
331##############################################################################
332
333def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp):
334 """Extracts all the names of functions in the given test case class
335 and its base classes that start with the given prefix. This is used
336 by makeSuite().
337 """
338 testFnNames = filter(lambda n,p=prefix: n[:len(p)] == p,
339 dir(testCaseClass))
340 for baseclass in testCaseClass.__bases__:
341 testFnNames = testFnNames + \
342 getTestCaseNames(baseclass, prefix, sortUsing=None)
343 if sortUsing:
344 testFnNames.sort(sortUsing)
345 return testFnNames
346
347
348def makeSuite(testCaseClass, prefix='test', sortUsing=cmp):
349 """Returns a TestSuite instance built from all of the test functions
350 in the given test case class whose names begin with the given
351 prefix. The cases are sorted by their function names
352 using the supplied comparison function, which defaults to 'cmp'.
353 """
354 cases = map(testCaseClass,
355 getTestCaseNames(testCaseClass, prefix, sortUsing))
356 return TestSuite(cases)
357
358
359def createTestInstance(name, module=None):
360 """Finds tests by their name, optionally only within the given module.
361
362 Return the newly-constructed test, ready to run. If the name contains a ':'
363 then the portion of the name after the colon is used to find a specific
364 test case within the test case class named before the colon.
365
366 Examples:
367 findTest('examples.listtests.suite')
368 -- returns result of calling 'suite'
369 findTest('examples.listtests.ListTestCase:checkAppend')
370 -- returns result of calling ListTestCase('checkAppend')
371 findTest('examples.listtests.ListTestCase:check-')
372 -- returns result of calling makeSuite(ListTestCase, prefix="check")
373 """
374
375 spec = string.split(name, ':')
376 if len(spec) > 2: raise ValueError, "illegal test name: %s" % name
377 if len(spec) == 1:
378 testName = spec[0]
379 caseName = None
380 else:
381 testName, caseName = spec
382 parts = string.split(testName, '.')
383 if module is None:
384 if len(parts) < 2:
385 raise ValueError, "incomplete test name: %s" % name
386 constructor = __import__(string.join(parts[:-1],'.'))
387 parts = parts[1:]
388 else:
389 constructor = module
390 for part in parts:
391 constructor = getattr(constructor, part)
392 if not callable(constructor):
393 raise ValueError, "%s is not a callable object" % constructor
394 if caseName:
395 if caseName[-1] == '-':
396 prefix = caseName[:-1]
397 if not prefix:
398 raise ValueError, "prefix too short: %s" % name
399 test = makeSuite(constructor, prefix=prefix)
400 else:
401 test = constructor(caseName)
402 else:
403 test = constructor()
404 if not hasattr(test,"countTestCases"):
405 raise TypeError, \
406 "object %s found with spec %s is not a test" % (test, name)
407 return test
408
409
410##############################################################################
411# Text UI
412##############################################################################
413
414class _WritelnDecorator:
415 """Used to decorate file-like objects with a handy 'writeln' method"""
416 def __init__(self,stream):
417 self.stream = stream
418 if _isJPython:
419 import java.lang.System
420 self.linesep = java.lang.System.getProperty("line.separator")
421 else:
422 self.linesep = os.linesep
423
424 def __getattr__(self, attr):
425 return getattr(self.stream,attr)
426
427 def writeln(self, *args):
428 if args: apply(self.write, args)
429 self.write(self.linesep)
430
431
432class _JUnitTextTestResult(TestResult):
433 """A test result class that can print formatted text results to a stream.
434
435 Used by JUnitTextTestRunner.
436 """
437 def __init__(self, stream):
438 self.stream = stream
439 TestResult.__init__(self)
440
441 def addError(self, test, error):
442 TestResult.addError(self,test,error)
443 self.stream.write('E')
444 self.stream.flush()
445 if error[0] is KeyboardInterrupt:
446 self.shouldStop = 1
447
448 def addFailure(self, test, error):
449 TestResult.addFailure(self,test,error)
450 self.stream.write('F')
451 self.stream.flush()
452
453 def startTest(self, test):
454 TestResult.startTest(self,test)
455 self.stream.write('.')
456 self.stream.flush()
457
458 def printNumberedErrors(self,errFlavour,errors):
459 if not errors: return
460 if len(errors) == 1:
461 self.stream.writeln("There was 1 %s:" % errFlavour)
462 else:
463 self.stream.writeln("There were %i %ss:" %
464 (len(errors), errFlavour))
465 i = 1
466 for test,error in errors:
467 errString = string.join(apply(traceback.format_exception,error),"")
468 self.stream.writeln("%i) %s" % (i, test))
469 self.stream.writeln(errString)
470 i = i + 1
471
472 def printErrors(self):
473 self.printNumberedErrors("error",self.errors)
474
475 def printFailures(self):
476 self.printNumberedErrors("failure",self.failures)
477
478 def printHeader(self):
479 self.stream.writeln()
480 if self.wasSuccessful():
481 self.stream.writeln("OK (%i tests)" % self.testsRun)
482 else:
483 self.stream.writeln("!!!FAILURES!!!")
484 self.stream.writeln("Test Results")
485 self.stream.writeln()
486 self.stream.writeln("Run: %i ; Failures: %i ; Errors: %i" %
487 (self.testsRun, len(self.failures),
488 len(self.errors)))
489
490 def printResult(self):
491 self.printHeader()
492 self.printErrors()
493 self.printFailures()
494
495
496class JUnitTextTestRunner:
497 """A test runner class that displays results in textual form.
498
499 The display format approximates that of JUnit's 'textui' test runner.
500 This test runner may be removed in a future version of PyUnit.
501 """
502 def __init__(self, stream=sys.stderr):
503 self.stream = _WritelnDecorator(stream)
504
505 def run(self, test):
506 "Run the given test case or test suite."
507 result = _JUnitTextTestResult(self.stream)
508 startTime = time.time()
509 test(result)
510 stopTime = time.time()
511 self.stream.writeln()
512 self.stream.writeln("Time: %.3fs" % float(stopTime - startTime))
513 result.printResult()
514 return result
515
516
517##############################################################################
518# Verbose text UI
519##############################################################################
520
521class _VerboseTextTestResult(TestResult):
522 """A test result class that can print formatted text results to a stream.
523
524 Used by VerboseTextTestRunner.
525 """
526 def __init__(self, stream, descriptions):
527 TestResult.__init__(self)
528 self.stream = stream
529 self.lastFailure = None
530 self.descriptions = descriptions
531
532 def startTest(self, test):
533 TestResult.startTest(self, test)
534 if self.descriptions:
535 self.stream.write(test.shortDescription() or str(test))
536 else:
537 self.stream.write(str(test))
538 self.stream.write(" ... ")
539
540 def stopTest(self, test):
541 TestResult.stopTest(self, test)
542 if self.lastFailure is not test:
543 self.stream.writeln("ok")
544
545 def addError(self, test, err):
546 TestResult.addError(self, test, err)
547 self._printError("ERROR", test, err)
548 self.lastFailure = test
549 if err[0] is KeyboardInterrupt:
550 self.shouldStop = 1
551
552 def addFailure(self, test, err):
553 TestResult.addFailure(self, test, err)
554 self._printError("FAIL", test, err)
555 self.lastFailure = test
556
557 def _printError(self, flavour, test, err):
558 errLines = []
559 separator1 = "\t" + '=' * 70
560 separator2 = "\t" + '-' * 70
561 if not self.lastFailure is test:
562 self.stream.writeln()
563 self.stream.writeln(separator1)
564 self.stream.writeln("\t%s" % flavour)
565 self.stream.writeln(separator2)
566 for line in apply(traceback.format_exception, err):
567 for l in string.split(line,"\n")[:-1]:
568 self.stream.writeln("\t%s" % l)
569 self.stream.writeln(separator1)
570
571
572class VerboseTextTestRunner:
573 """A test runner class that displays results in textual form.
574
575 It prints out the names of tests as they are run, errors as they
576 occur, and a summary of the results at the end of the test run.
577 """
578 def __init__(self, stream=sys.stderr, descriptions=1):
579 self.stream = _WritelnDecorator(stream)
580 self.descriptions = descriptions
581
582 def run(self, test):
583 "Run the given test case or test suite."
584 result = _VerboseTextTestResult(self.stream, self.descriptions)
585 startTime = time.time()
586 test(result)
587 stopTime = time.time()
588 timeTaken = float(stopTime - startTime)
589 self.stream.writeln("-" * 78)
590 run = result.testsRun
591 self.stream.writeln("Ran %d test%s in %.3fs" %
592 (run, run > 1 and "s" or "", timeTaken))
593 self.stream.writeln()
594 if not result.wasSuccessful():
595 self.stream.write("FAILED (")
596 failed, errored = map(len, (result.failures, result.errors))
597 if failed:
598 self.stream.write("failures=%d" % failed)
599 if errored:
600 if failed: self.stream.write(", ")
601 self.stream.write("errors=%d" % errored)
602 self.stream.writeln(")")
603 else:
604 self.stream.writeln("OK")
605 return result
606
607
608# Which flavour of TextTestRunner is the default?
609TextTestRunner = VerboseTextTestRunner
610
611
612##############################################################################
613# Facilities for running tests from the command line
614##############################################################################
615
616class TestProgram:
617 """A command-line program that runs a set of tests; this is primarily
618 for making test modules conveniently executable.
619 """
620 USAGE = """\
621Usage: %(progName)s [-h|--help] [test[:(casename|prefix-)]] [...]
622
623Examples:
624 %(progName)s - run default set of tests
625 %(progName)s MyTestSuite - run suite 'MyTestSuite'
626 %(progName)s MyTestCase:checkSomething - run MyTestCase.checkSomething
627 %(progName)s MyTestCase:check- - run all 'check*' test methods
628 in MyTestCase
629"""
630 def __init__(self, module='__main__', defaultTest=None,
631 argv=None, testRunner=None):
632 if type(module) == type(''):
633 self.module = __import__(module)
634 for part in string.split(module,'.')[1:]:
635 self.module = getattr(self.module, part)
636 else:
637 self.module = module
638 if argv is None:
639 argv = sys.argv
640 self.defaultTest = defaultTest
641 self.testRunner = testRunner
642 self.progName = os.path.basename(argv[0])
643 self.parseArgs(argv)
644 self.createTests()
645 self.runTests()
646
647 def usageExit(self, msg=None):
648 if msg: print msg
649 print self.USAGE % self.__dict__
650 sys.exit(2)
651
652 def parseArgs(self, argv):
653 import getopt
654 try:
655 options, args = getopt.getopt(argv[1:], 'hH', ['help'])
656 opts = {}
657 for opt, value in options:
658 if opt in ('-h','-H','--help'):
659 self.usageExit()
660 if len(args) == 0 and self.defaultTest is None:
661 raise getopt.error, "No default test is defined."
662 if len(args) > 0:
663 self.testNames = args
664 else:
665 self.testNames = (self.defaultTest,)
666 except getopt.error, msg:
667 self.usageExit(msg)
668
669 def createTests(self):
670 tests = []
671 for testName in self.testNames:
672 tests.append(createTestInstance(testName, self.module))
673 self.test = TestSuite(tests)
674
675 def runTests(self):
676 if self.testRunner is None:
677 self.testRunner = TextTestRunner()
678 result = self.testRunner.run(self.test)
679 sys.exit(not result.wasSuccessful())
680
681main = TestProgram
682
683
684##############################################################################
685# Executing this module from the command line
686##############################################################################
687
688if __name__ == "__main__":
689 main(module=None)