unittest test skipping - Code Review (original) (raw)
OLD
NEW
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 '''
2 '''
3 Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's
3 Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's
4 Smalltalk testing framework.
4 Smalltalk testing framework.
5
5
6 This module contains the core framework classes that form the basis of
6 This module contains the core framework classes that form the basis of
7 specific test cases and suites (TestCase, TestSuite etc.), and also a
7 specific test cases and suites (TestCase, TestSuite etc.), and also a
8 text-based utility class for running the tests and reporting the results
8 text-based utility class for running the tests and reporting the results
9 (TextTestRunner).
9 (TextTestRunner).
10
10
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading...
46
46
47 __author__ = "Steve Purcell"
47 __author__ = "Steve Purcell"
48 __email__ = "stephen_purcell at yahoo dot com"
48 __email__ = "stephen_purcell at yahoo dot com"
49 __version__ = "#Revision: 1.63 $"[11:-2]
49 __version__ = "#Revision: 1.63 $"[11:-2]
50
50
51 import time
51 import time
52 import sys
52 import sys
53 import traceback
53 import traceback
54 import os
54 import os
55 import types
55 import types
56 import functools
56
57
57 ##############################################################################
58 ##############################################################################
58 # Exported classes and functions
59 # Exported classes and functions
59 ##############################################################################
60 ##############################################################################
60 __all__ = ['TestResult', 'TestCase', 'TestSuite', 'TextTestRunner',
61 __all__ = ['TestResult', 'TestCase', 'TestSuite', 'TextTestRunner',
61 'TestLoader', 'FunctionTestCase', 'main', 'defaultTestLoader']
62 'TestLoader', 'FunctionTestCase', 'main', 'defaultTestLoader']
62
63
63 # Expose obsolete functions for backwards compatibility
64 # Expose obsolete functions for backwards compatibility
64 __all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
65 __all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
65
66
(...skipping 11 matching lines...) Expand all Loading...
77 return mycmp(self.obj, other.obj) == -1
78 return mycmp(self.obj, other.obj) == -1
78 return K
79 return K
79
80
80 ##############################################################################
81 ##############################################################################
81 # Test framework core
82 # Test framework core
82 ##############################################################################
83 ##############################################################################
83
84
84 def _strclass(cls):
85 def _strclass(cls):
85 return "%s.%s" % (cls.__module__, cls.__name__)
86 return "%s.%s" % (cls.__module__, cls.__name__)
86
87
88
89 class SkipTest(Exception):
90 """
91 Raise this exception in a test to skip it.
92
93 Usually you can use TestResult.skip() or one of the skipping decorators
94 instead of raising this directly.
95 """
96 pass
97
98 class _ExpectedFailure(SkipTest):
99 """
100 Raise this when a test is expected to fail.
101
102 This is an implementation detail.
103 """
104
105 def __init__(self, exc_info):
106 super(_ExpectedFailure, self).__init__()
107 self.exc_info = exc_info
108
109 def _id(obj):
110 return obj
111
112 def skip(reason):
113 """
114 Unconditionally skip a test.
115 """
116 def decorator(test_item):
117 if isinstance(test_item, type) and issubclass(test_item, TestCase):
118 test_item.__unittest_skip__ = True
119 test_item.__unittest_skip_why__ = reason
120 return test_item
121 @functools.wraps(test_item)
122 def skip_wrapper(*args, **kwargs):
123 raise SkipTest(reason)
124 return skip_wrapper
125 return decorator
126
127 def skipIf(condition, reason):
128 """
129 Skip a test if the the condition is true.
130 """
131 if condition:
132 return skip(reason)
133 return _id
134
135 def skipUnless(condition, reason):
136 """
137 Skip a test unless the condition is true.
138 """
139 if not condition:
140 return skip(reason)
141 return _id
142
143
144 _unexpected_success = object()
145
146 def expectedFailure(func):
147 @functools.wraps(func)
148 def wrapper(*args, **kwargs):
149 try:
150 func(*args, **kwargs)
151 except Exception:
152 raise _ExpectedFailure(sys.exc_info())
153 return _unexpected_success
154 return wrapper
155
156
87 __unittest = 1
157 __unittest = 1
88
158
89 class TestResult(object):
159 class TestResult(object):
90 """Holder for test result information.
160 """Holder for test result information.
91
161
92 Test results are automatically managed by the TestCase and TestSuite
162 Test results are automatically managed by the TestCase and TestSuite
93 classes, and do not need to be explicitly manipulated by writers of tests.
163 classes, and do not need to be explicitly manipulated by writers of tests.
94
164
95 Each instance holds the total number of tests run, and collections of
165 Each instance holds the total number of tests run, and collections of
96 failures and errors that occurred among those test runs. The collections
166 failures and errors that occurred among those test runs. The collections
97 contain tuples of (testcase, exceptioninfo), where exceptioninfo is the
167 contain tuples of (testcase, exceptioninfo), where exceptioninfo is the
98 formatted traceback of the error that occurred.
168 formatted traceback of the error that occurred.
99 """
169 """
100 def __init__(self):
170 def __init__(self):
101 self.failures = []
171 self.failures = []
102 self.errors = []
172 self.errors = []
103 self.testsRun = 0
173 self.testsRun = 0
174 self.skipped = []
175 self.expected_failures = []
176 self.unexpected_successes = []
104 self.shouldStop = False
177 self.shouldStop = False
105
178
106 def startTest(self, test):
179 def startTest(self, test):
107 "Called when the given test is about to be run"
180 "Called when the given test is about to be run"
108 self.testsRun = self.testsRun + 1
181 self.testsRun = self.testsRun + 1
109
182
110 def stopTest(self, test):
183 def stopTest(self, test):
111 "Called when the given test has been run"
184 "Called when the given test has been run"
112 pass
185 pass
113
186
114 def addError(self, test, err):
187 def addError(self, test, err):
115 """Called when an error has occurred. 'err' is a tuple of values as
188 """Called when an error has occurred. 'err' is a tuple of values as
116 returned by sys.exc_info().
189 returned by sys.exc_info().
117 """
190 """
118 self.errors.append((test, self._exc_info_to_string(err, test)))
191 self.errors.append((test, self._exc_info_to_string(err, test)))
119
192
120 def addFailure(self, test, err):
193 def addFailure(self, test, err):
121 """Called when an error has occurred. 'err' is a tuple of values as
194 """Called when an error has occurred. 'err' is a tuple of values as
122 returned by sys.exc_info()."""
195 returned by sys.exc_info()."""
123 self.failures.append((test, self._exc_info_to_string(err, test)))
196 self.failures.append((test, self._exc_info_to_string(err, test)))
124
197
125 def addSuccess(self, test):
198 def addSuccess(self, test):
126 "Called when a test has completed successfully"
199 "Called when a test has completed successfully"
127 pass
200 pass
128
201
202 def addSkip(self, test, reason):
203 """Called when a test is skipped."""
204 self.skipped.append((test, reason))
205
206 def addExpectedFailure(self, test, err):
207 """Called when an expected failure/error occured."""
208 self.expected_failures.append(
209 (test, self._exc_info_to_string(err, test)))
210
211 def addUnexpectedSuccess(self, test):
212 """Called when a test was expected to failure, but succeed."""
213 self.unexpected_successes.append(test)
214
129 def wasSuccessful(self):
215 def wasSuccessful(self):
130 "Tells whether or not this result was a success"
216 "Tells whether or not this result was a success"
131 return len(self.failures) == len(self.errors) == 0
217 return len(self.failures) == len(self.errors) == 0
132
218
133 def stop(self):
219 def stop(self):
134 "Indicates that the tests should be aborted"
220 "Indicates that the tests should be aborted"
135 self.shouldStop = True
221 self.shouldStop = True
136
222
137 def _exc_info_to_string(self, err, test):
223 def _exc_info_to_string(self, err, test):
138 """Converts a sys.exc_info()-style tuple of values into a string."""
224 """Converts a sys.exc_info()-style tuple of values into a string."""
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading...
269
355
270 def run(self, result=None):
356 def run(self, result=None):
271 if result is None: result = self.defaultTestResult()
357 if result is None: result = self.defaultTestResult()
272 result.startTest(self)
358 result.startTest(self)
273 testMethod = getattr(self, self._testMethodName)
359 testMethod = getattr(self, self._testMethodName)
274 try:
360 try:
275 try:
361 try:
276 self.setUp()
362 self.setUp()
277 except Exception:
363 except Exception:
278 result.addError(self, self._exc_info())
364 result.addError(self, self._exc_info())
279 return
365 return
280
366
281 ok = False
367 success = False
282 try:
368 try:
283 testMethod()
369 test_returned = testMethod()
284 ok = True
285 except self.failureException:
370 except self.failureException:
286 result.addFailure(self, self._exc_info())
371 result.addFailure(self, self._exc_info())
372 except _ExpectedFailure as e:
373 result.addExpectedFailure(self, e.exc_info)
374 except SkipTest as e:
375 result.addSkip(self, str(e))
287 except Exception:
376 except Exception:
288 result.addError(self, self._exc_info())
377 result.addError(self, self._exc_info())
378 else:
379 if test_returned is _unexpected_success:
380 result.addUnexpectedSuccess(self)
381 else:
382 success = True
289
383
290 try:
384 try:
291 self.tearDown()
385 self.tearDown()
292 except Exception:
386 except Exception:
293 result.addError(self, self._exc_info())
387 result.addError(self, self._exc_info())
294 ok = False
388 success = False
295 if ok: result.addSuccess(self)
389 if success:
390 result.addSuccess(self)
296 finally:
391 finally:
297 result.stopTest(self)
392 result.stopTest(self)
298
393
299 def __call__(self, *args, **kwds):
394 def __call__(self, *args, **kwds):
300 return self.run(*args, **kwds)
395 return self.run(*args, **kwds)
301
396
302 def debug(self):
397 def debug(self):
303 """Run the test without collecting errors in a TestResult"""
398 """Run the test without collecting errors in a TestResult"""
304 self.setUp()
399 self.setUp()
305 getattr(self, self._testMethodName)()
400 getattr(self, self._testMethodName)()
306 self.tearDown()
401 self.tearDown()
307
402
308 def _exc_info(self):
403 def _exc_info(self):
309 """Return a version of sys.exc_info() with the traceback frame
404 """Return a version of sys.exc_info() with the traceback frame
310 minimised; usually the top level of the traceback frame is not
405 minimised; usually the top level of the traceback frame is not
311 needed.
406 needed.
312 """
407 """
313 return sys.exc_info()
408 return sys.exc_info()
314
409
410 def skip(self, reason):
411 """Skip this test."""
412 raise SkipTest(reason)
413
315 def fail(self, msg=None):
414 def fail(self, msg=None):
316 """Fail immediately, with the given message."""
415 """Fail immediately, with the given message."""
317 raise self.failureException(msg)
416 raise self.failureException(msg)
318
417
319 def failIf(self, expr, msg=None):
418 def failIf(self, expr, msg=None):
320 "Fail the test if the expression is true."
419 "Fail the test if the expression is true."
321 if expr: raise self.failureException(msg)
420 if expr: raise self.failureException(msg)
322
421
323 def failUnless(self, expr, msg=None):
422 def failUnless(self, expr, msg=None):
324 """Fail the test unless the expression is true."""
423 """Fail the test unless the expression is true."""
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading...
412 def __init__(self, tests=()):
511 def __init__(self, tests=()):
413 self._tests = []
512 self._tests = []
414 self.addTests(tests)
513 self.addTests(tests)
415
514
416 def __repr__(self):
515 def __repr__(self):
417 return "<%s tests=%s>" % (_strclass(self.__class__), self._tests)
516 return "<%s tests=%s>" % (_strclass(self.__class__), self._tests)
418
517
419 __str__ = __repr__
518 __str__ = __repr__
420
519
421 def __eq__(self, other):
520 def __eq__(self, other):
422 if type(self) is not type(other):
521 if not isinstance(other, self.__class__):
423 return False
522 return NotImplemented
424 return self._tests == other._tests
523 return self._tests == other._tests
425
524
426 def __ne__(self, other):
525 def __ne__(self, other):
427 return not self == other
526 return not self == other
428
527
429 # Can't guarantee hash invariant, so flag as unhashable
528 # Can't guarantee hash invariant, so flag as unhashable
430 __hash__ = None
529 __hash__ = None
431
530
432 def __iter__(self):
531 def __iter__(self):
433 return iter(self._tests)
532 return iter(self._tests)
(...skipping 28 matching lines...) Expand all Loading...
462 return result
561 return result
463
562
464 def __call__(self, *args, **kwds):
563 def __call__(self, *args, **kwds):
465 return self.run(*args, **kwds)
564 return self.run(*args, **kwds)
466
565
467 def debug(self):
566 def debug(self):
468 """Run the tests without collecting errors in a TestResult"""
567 """Run the tests without collecting errors in a TestResult"""
469 for test in self._tests: test.debug()
568 for test in self._tests: test.debug()
470
569
471
570
571 class ClassTestSuite(TestSuite):
572 """
573 Suite of tests derived from a single TestCase class.
574 """
575
576 def __init__(self, tests, class_collected_from):
577 super(ClassTestSuite, self).__init__(tests)
578 self.collected_from = class_collected_from
579
580 def id(self):
581 module = getattr(self.collected_from, "__module__", None)
582 if module is not None:
583 return "{0}.{1}".format(module, self.collected_from.__name__)
584 return self.collected_from.__name__
585
586 def run(self, result):
587 if getattr(self.collected_from, "__unittest_skip__", False):
588 # ClassTestSuite result pretends to be a TestCase enough to be
589 # reported.
590 result.startTest(self)
591 try:
592 result.addSkip(self, self.collected_from.__unittest_skip_why__)
593 finally:
594 result.stopTest(self)
595 else:
596 result = super(ClassTestSuite, self).run(result)
597 return result
598
599 shortDescription = id
600
601
472 class FunctionTestCase(TestCase):
602 class FunctionTestCase(TestCase):
473 """A test case that wraps a test function.
603 """A test case that wraps a test function.
474
604
475 This is useful for slipping pre-existing test functions into the
605 This is useful for slipping pre-existing test functions into the
476 unittest framework. Optionally, set-up and tidy-up functions can be
606 unittest framework. Optionally, set-up and tidy-up functions can be
477 supplied. As with TestCase, the tidy-up ('tearDown') function will
607 supplied. As with TestCase, the tidy-up ('tearDown') function will
478 always be called if the set-up ('setUp') function ran successfully.
608 always be called if the set-up ('setUp') function ran successfully.
479 """
609 """
480
610
481 def __init__(self, testFunc, setUp=None, tearDown=None,
611 def __init__(self, testFunc, setUp=None, tearDown=None,
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading...
533 # Locating and loading tests
663 # Locating and loading tests
534 ##############################################################################
664 ##############################################################################
535
665
536 class TestLoader(object):
666 class TestLoader(object):
537 """This class is responsible for loading tests according to various
667 """This class is responsible for loading tests according to various
538 criteria and returning them wrapped in a TestSuite
668 criteria and returning them wrapped in a TestSuite
539 """
669 """
540 testMethodPrefix = 'test'
670 testMethodPrefix = 'test'
541 sortTestMethodsUsing = cmp
671 sortTestMethodsUsing = cmp
542 suiteClass = TestSuite
672 suiteClass = TestSuite
673 classSuiteClass = ClassTestSuite
543
674
544 def loadTestsFromTestCase(self, testCaseClass):
675 def loadTestsFromTestCase(self, testCaseClass):
545 """Return a suite of all tests cases contained in testCaseClass"""
676 """Return a suite of all tests cases contained in testCaseClass"""
546 if issubclass(testCaseClass, TestSuite):
677 if issubclass(testCaseClass, TestSuite):
547 raise TypeError("Test cases should not be derived from TestSuite. Ma ybe you meant to derive from TestCase?")
678 raise TypeError("Test cases should not be derived from TestSuite. Ma ybe you meant to derive from TestCase?")
548 testCaseNames = self.getTestCaseNames(testCaseClass)
679 testCaseNames = self.getTestCaseNames(testCaseClass)
549 if not testCaseNames and hasattr(testCaseClass, 'runTest'):
680 if not testCaseNames and hasattr(testCaseClass, 'runTest'):
550 testCaseNames = ['runTest']
681 testCaseNames = ['runTest']
551 return self.suiteClass(map(testCaseClass, testCaseNames))
682 suite = self.classSuiteClass(map(testCaseClass, testCaseNames),
683 testCaseClass)
684 return suite
552
685
553 def loadTestsFromModule(self, module):
686 def loadTestsFromModule(self, module):
554 """Return a suite of all tests cases contained in the given module"""
687 """Return a suite of all tests cases contained in the given module"""
555 tests = []
688 tests = []
556 for name in dir(module):
689 for name in dir(module):
557 obj = getattr(module, name)
690 obj = getattr(module, name)
558 if (isinstance(obj, (type, types.ClassType)) and
691 if (isinstance(obj, (type, types.ClassType)) and
559 issubclass(obj, TestCase)):
692 issubclass(obj, TestCase)):
560 tests.append(self.loadTestsFromTestCase(obj))
693 tests.append(self.loadTestsFromTestCase(obj))
561 return self.suiteClass(tests)
694 return self.suiteClass(tests)
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading...
712 self.stream.flush()
845 self.stream.flush()
713
846
714 def addFailure(self, test, err):
847 def addFailure(self, test, err):
715 TestResult.addFailure(self, test, err)
848 TestResult.addFailure(self, test, err)
716 if self.showAll:
849 if self.showAll:
717 self.stream.writeln("FAIL")
850 self.stream.writeln("FAIL")
718 elif self.dots:
851 elif self.dots:
719 self.stream.write('F')
852 self.stream.write('F')
720 self.stream.flush()
853 self.stream.flush()
721
854
855 def addSkip(self, test, reason):
856 TestResult.addSkip(self, test, reason)
857 if self.showAll:
858 self.stream.writeln("skipped {0!r}".format(reason))
859 elif self.dots:
860 self.stream.write("s")
861 self.stream.flush()
862
863 def addExpectedFailure(self, test, err):
864 TestResult.addExpectedFailure(self, test, err)
865 if self.showAll:
866 self.stream.writeln("expected failure")
867 elif self.dots:
868 self.stream.write(".")
869 self.stream.flush()
870
871 def addUnexpectedSuccess(self, test):
872 TestResult.addUnexpectedSuccess(self, test)
873 if self.showAll:
874 self.stream.writeln("unexpected success")
875 elif self.dots:
876 self.stream.write(".")
877 self.stream.flush()
878
722 def printErrors(self):
879 def printErrors(self):
723 if self.dots or self.showAll:
880 if self.dots or self.showAll:
724 self.stream.writeln()
881 self.stream.writeln()
725 self.printErrorList('ERROR', self.errors)
882 self.printErrorList('ERROR', self.errors)
726 self.printErrorList('FAIL', self.failures)
883 self.printErrorList('FAIL', self.failures)
727
884
728 def printErrorList(self, flavour, errors):
885 def printErrorList(self, flavour, errors):
729 for test, err in errors:
886 for test, err in errors:
730 self.stream.writeln(self.separator1)
887 self.stream.writeln(self.separator1)
731 self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
888 self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
(...skipping 21 matching lines...) Expand all Loading...
753 startTime = time.time()
910 startTime = time.time()
754 test(result)
911 test(result)
755 stopTime = time.time()
912 stopTime = time.time()
756 timeTaken = stopTime - startTime
913 timeTaken = stopTime - startTime
757 result.printErrors()
914 result.printErrors()
758 self.stream.writeln(result.separator2)
915 self.stream.writeln(result.separator2)
759 run = result.testsRun
916 run = result.testsRun
760 self.stream.writeln("Ran %d test%s in %.3fs" %
917 self.stream.writeln("Ran %d test%s in %.3fs" %
761 (run, run != 1 and "s" or "", timeTaken))
918 (run, run != 1 and "s" or "", timeTaken))
762 self.stream.writeln()
919 self.stream.writeln()
920 results = map(len, (result.expected_failures,
921 result.unexpected_successes,
922 result.skipped))
923 expected_fails, unexpected_successes, skipped = results
924 infos = []
763 if not result.wasSuccessful():
925 if not result.wasSuccessful():
764 self.stream.write("FAILED (")
926 self.stream.write("FAILED")
765 failed, errored = map(len, (result.failures, result.errors))
927 failed, errored = map(len, (result.failures, result.errors))
766 if failed:
928 if failed:
767 self.stream.write("failures=%d" % failed)
929 infos.append("failures=%d" % failed)
768 if errored:
930 if errored:
769 if failed: self.stream.write(", ")
931 infos.append("errors=%d" % errored)
770 self.stream.write("errors=%d" % errored)
771 self.stream.writeln(")")
772 else:
932 else:
773 self.stream.writeln("OK")
933 self.stream.write("OK")
934 if skipped:
935 infos.append("skipped=%d" % skipped)
936 if expected_fails:
937 infos.append("expected failures=%d" % expected_fails)
938 if unexpected_successes:
939 infos.append("unexpected successes=%d" % unexpected_successes)
940 if infos:
941 self.stream.writeln(" (%s)" % (", ".join(infos),))
774 return result
942 return result
775
943
776
944
777
945
778 ##############################################################################
946 ##############################################################################
779 # Facilities for running tests from the command line
947 # Facilities for running tests from the command line
780 ##############################################################################
948 ##############################################################################
781
949
782 class TestProgram(object):
950 class TestProgram(object):
783 """A command-line program that runs a set of tests; this is primarily
951 """A command-line program that runs a set of tests; this is primarily
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading...
817 self.parseArgs(argv)
985 self.parseArgs(argv)
818 self.runTests()
986 self.runTests()
819
987
820 def usageExit(self, msg=None):
988 def usageExit(self, msg=None):
821 if msg: print msg
989 if msg: print msg
822 print self.USAGE % self.__dict__
990 print self.USAGE % self.__dict__
823 sys.exit(2)
991 sys.exit(2)
824
992
825 def parseArgs(self, argv):
993 def parseArgs(self, argv):
826 import getopt
994 import getopt
995 long_opts = ['help','verbose','quiet']
827 try:
996 try:
828 options, args = getopt.getopt(argv[1:], 'hHvq',
997 options, args = getopt.getopt(argv[1:], 'hHvq', long_opts)
829 ['help','verbose','quiet'])
830 for opt, value in options:
998 for opt, value in options:
831 if opt in ('-h','-H','--help'):
999 if opt in ('-h','-H','--help'):
832 self.usageExit()
1000 self.usageExit()
833 if opt in ('-q','--quiet'):
1001 if opt in ('-q','--quiet'):
834 self.verbosity = 0
1002 self.verbosity = 0
835 if opt in ('-v','--verbose'):
1003 if opt in ('-v','--verbose'):
836 self.verbosity = 2
1004 self.verbosity = 2
837 if len(args) == 0 and self.defaultTest is None:
1005 if len(args) == 0 and self.defaultTest is None:
838 self.test = self.testLoader.loadTestsFromModule(self.module)
1006 self.test = self.testLoader.loadTestsFromModule(self.module)
839 return
1007 return
(...skipping 24 matching lines...) Expand all Loading...
864
1032
865 main = TestProgram
1033 main = TestProgram
866
1034
867
1035
868 ##############################################################################
1036 ##############################################################################
869 # Executing this module from the command line
1037 # Executing this module from the command line
870 ##############################################################################
1038 ##############################################################################
871
1039
872 if __name__ == "__main__":
1040 if __name__ == "__main__":
873 main(module=None)
1041 main(module=None)
OLD
NEW