Commit 5254c04a authored by Benjamin Peterson's avatar Benjamin Peterson

Merged revisions 70555 via svnmerge from

svn+ssh://pythondev@svn.python.org/python/trunk

........
  r70555 | benjamin.peterson | 2009-03-23 16:50:21 -0500 (Mon, 23 Mar 2009) | 4 lines

  implement test skipping and expected failures

  patch by myself #1034053
........
parent 87d98bcb
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
.. sectionauthor:: Raymond Hettinger <python@rcn.com> .. sectionauthor:: Raymond Hettinger <python@rcn.com>
.. versionchanged:: 3.1
Added :ref:`skipping and expected failures <unittest-skipping>`.
The Python unit testing framework, sometimes referred to as "PyUnit," is a The Python unit testing framework, sometimes referred to as "PyUnit," is a
Python language version of JUnit, by Kent Beck and Erich Gamma. JUnit is, in Python language version of JUnit, by Kent Beck and Erich Gamma. JUnit is, in
turn, a Java version of Kent's Smalltalk testing framework. Each is the de turn, a Java version of Kent's Smalltalk testing framework. Each is the de
...@@ -58,7 +62,8 @@ fixture is created for each test. ...@@ -58,7 +62,8 @@ fixture is created for each test.
Test suites are implemented by the :class:`TestSuite` class. This class allows Test suites are implemented by the :class:`TestSuite` class. This class allows
individual tests and test suites to be aggregated; when the suite is executed, individual tests and test suites to be aggregated; when the suite is executed,
all tests added directly to the suite and in "child" test suites are run. all tests added directly to the suite and in "child" test suites are run. A
:class:`ClassTestSuite` contains the test cases of a class.
A test runner is an object that provides a single method, :meth:`run`, which A test runner is an object that provides a single method, :meth:`run`, which
accepts a :class:`TestCase` or :class:`TestSuite` object as a parameter, and accepts a :class:`TestCase` or :class:`TestSuite` object as a parameter, and
...@@ -407,6 +412,78 @@ may treat :exc:`AssertionError` differently. ...@@ -407,6 +412,78 @@ may treat :exc:`AssertionError` differently.
make future test refactorings infinitely easier. make future test refactorings infinitely easier.
.. _unittest-skipping:
Skipping tests and expected failures
------------------------------------
Unittest supports skipping individual test methods and even whole classes of
tests. In addition, it supports marking a test as a "expected failure," a test
that is broken and will fail, but shouldn't be counted as a failure on a
:class:`TestResult`.
Skipping a test is simply a matter of using the :func:`skip` :term:`decorator`
or one of its conditional variants.
Basic skipping looks like this: ::
class MyTestCase(unittest.TestCase):
@unittest.skip("demonstrating skipping")
def test_nothing(self):
self.fail("shouldn't happen")
This is the output of running the example above in verbose mode: ::
test_nothing (__main__.MyTestCase) ... skipped 'demonstrating skipping'
----------------------------------------------------------------------
Ran 1 test in 0.072s
Classes can be skipped just like methods: ::
@skip("showing class skipping")
class MySkippedTestCase(unittest.TestCase):
def test_not_run(self):
pass
Expected failures use the :func:`expectedFailure` decorator. ::
class ExpectedFailureTestCase(unittest.TestCase):
@unittest.expectedFailure
def test_fail(self):
self.assertEqual(1, 0, "broken")
It's easy to roll your own skipping decorators by making a decorator that calls
:func:`skip` on the test when it wants it to be skipped. This decorator skips
the test unless the passed object has a certain attribute: ::
def skipUnlessHasattr(obj, attr):
if hasattr(obj, attr):
return lambda func: func
return unittest.skip("{0!r} doesn't have {1!r}".format(obj, attr))
The following decorators implement test skipping and expected failures:
.. function:: skip(reason)
Unconditionally skip the decorated test. *reason* should describe why the
test is being skipped.
.. function:: skipIf(condition, reason)
Skip the decorated test if *condition* is true.
.. function:: skipUnless(condition, reason)
Skip the decoratored test unless *condition* is true.
.. function:: expectedFailure
Mark the test as an expected failure. If the test fails when run, the test
is not counted as a failure.
.. _unittest-contents: .. _unittest-contents:
Classes and functions Classes and functions
...@@ -458,6 +535,13 @@ Classes and functions ...@@ -458,6 +535,13 @@ Classes and functions
test suites that will be used to build the suite initially. Additional methods test suites that will be used to build the suite initially. Additional methods
are provided to add test cases and suites to the collection later on. are provided to add test cases and suites to the collection later on.
.. class:: ClassTestSuite(tests, collected_from)
This subclass of :class:`TestSuite` repesents an aggregation of individuals
tests from one :class:`TestCase` class. *tests* is an iterable of
:class:`TestCase` instances created from the class. *collected_from* is the
class they came from.
.. class:: TestLoader() .. class:: TestLoader()
...@@ -550,6 +634,11 @@ Methods in the first group (running the test) are: ...@@ -550,6 +634,11 @@ Methods in the first group (running the test) are:
The same effect may be had by simply calling the :class:`TestCase` instance. The same effect may be had by simply calling the :class:`TestCase` instance.
.. method:: TestCase.skip(reason)
Skips the current test. See :ref:`unittest-skipping`.
.. method:: TestCase.debug() .. method:: TestCase.debug()
Run the test without collecting the result. This allows exceptions raised by Run the test without collecting the result. This allows exceptions raised by
...@@ -685,10 +774,11 @@ test: ...@@ -685,10 +774,11 @@ test:
TestSuite Objects TestSuite Objects
----------------- -----------------
:class:`TestSuite` objects behave much like :class:`TestCase` objects, except :class:`TestSuite` (including :class:`ClassTestSuite`) objects behave much like
they do not actually implement a test. Instead, they are used to aggregate :class:`TestCase` objects, except they do not actually implement a test.
tests into groups of tests that should be run together. Some additional methods Instead, they are used to aggregate tests into groups of tests that should be
are available to add tests to :class:`TestSuite` instances: run together. Some additional methods are available to add tests to
:class:`TestSuite` instances:
.. method:: TestSuite.addTest(test) .. method:: TestSuite.addTest(test)
...@@ -835,6 +925,34 @@ tools which support interactive reporting while tests are being run. ...@@ -835,6 +925,34 @@ tools which support interactive reporting while tests are being run.
The default implementation does nothing. The default implementation does nothing.
.. method:: TestResult.addSkip(test, reason)
Called when the test case *test* is skipped. *reason* is the reason the test
gave for skipping.
The default implementation appends a tuple ``(test, reason)`` to the
instance's ``skipped`` attribute.
.. method:: TestResult.addExpectedFailure(test, err)
Called when the test case *test* fails, but was marked with the
:func:`expectedFailure` decorator.
The default implementation appends a tuple ``(test, formatted_err)`` to the
instance's ``expected_failures`` attribute, where *formatted_err* is a
formatted traceback derived from *err*.
.. method:: TestResult.addUnexpectedSuccess(test)
Called when the test case *test* was marked with the :func:`expectedFailure`
decorator, but succeeded.
The default implementation appends the test to the instance's
``unexpected_successes`` attribute.
.. _testloader-objects: .. _testloader-objects:
TestLoader Objects TestLoader Objects
...@@ -939,3 +1057,9 @@ subclassing or assignment on an instance: ...@@ -939,3 +1057,9 @@ subclassing or assignment on an instance:
This affects all the :meth:`loadTestsFrom\*` methods. This affects all the :meth:`loadTestsFrom\*` methods.
.. attribute:: TestLoader.classSuiteClass
Callable object that constructs a test suite for the tests cases from one
class. The default value is :class:`ClassTestSuite`.
...@@ -31,10 +31,27 @@ class LoggingResult(unittest.TestResult): ...@@ -31,10 +31,27 @@ class LoggingResult(unittest.TestResult):
self._events.append('addFailure') self._events.append('addFailure')
super().addFailure(*args) super().addFailure(*args)
def addSuccess(self, *args):
self._events.append('addSuccess')
super(LoggingResult, self).addSuccess(*args)
def addError(self, *args): def addError(self, *args):
self._events.append('addError') self._events.append('addError')
super().addError(*args) super().addError(*args)
def addSkip(self, *args):
self._events.append('addSkip')
super(LoggingResult, self).addSkip(*args)
def addExpectedFailure(self, *args):
self._events.append('addExpectedFailure')
super(LoggingResult, self).addExpectedFailure(*args)
def addUnexpectedSuccess(self, *args):
self._events.append('addUnexpectedSuccess')
super(LoggingResult, self).addUnexpectedSuccess(*args)
class TestEquality(object): class TestEquality(object):
# Check for a valid __eq__ implementation # Check for a valid __eq__ implementation
def test_eq(self): def test_eq(self):
...@@ -72,6 +89,13 @@ class TestHashing(object): ...@@ -72,6 +89,13 @@ class TestHashing(object):
self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e)) self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e))
# List subclass we can add attributes to.
class MyClassSuite(list):
def __init__(self, tests, klass):
super(MyClassSuite, self).__init__(tests)
################################################################ ################################################################
### /Support code ### /Support code
...@@ -1233,7 +1257,7 @@ class Test_TestLoader(TestCase): ...@@ -1233,7 +1257,7 @@ class Test_TestLoader(TestCase):
tests = [Foo('test_1'), Foo('test_2')] tests = [Foo('test_1'), Foo('test_2')]
loader = unittest.TestLoader() loader = unittest.TestLoader()
loader.suiteClass = list loader.classSuiteClass = MyClassSuite
self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) self.assertEqual(loader.loadTestsFromTestCase(Foo), tests)
# It is implicit in the documentation for TestLoader.suiteClass that # It is implicit in the documentation for TestLoader.suiteClass that
...@@ -1246,7 +1270,7 @@ class Test_TestLoader(TestCase): ...@@ -1246,7 +1270,7 @@ class Test_TestLoader(TestCase):
def foo_bar(self): pass def foo_bar(self): pass
m.Foo = Foo m.Foo = Foo
tests = [[Foo('test_1'), Foo('test_2')]] tests = [unittest.ClassTestSuite([Foo('test_1'), Foo('test_2')], Foo)]
loader = unittest.TestLoader() loader = unittest.TestLoader()
loader.suiteClass = list loader.suiteClass = list
...@@ -1265,7 +1289,7 @@ class Test_TestLoader(TestCase): ...@@ -1265,7 +1289,7 @@ class Test_TestLoader(TestCase):
tests = [Foo('test_1'), Foo('test_2')] tests = [Foo('test_1'), Foo('test_2')]
loader = unittest.TestLoader() loader = unittest.TestLoader()
loader.suiteClass = list loader.classSuiteClass = MyClassSuite
self.assertEqual(loader.loadTestsFromName('Foo', m), tests) self.assertEqual(loader.loadTestsFromName('Foo', m), tests)
# It is implicit in the documentation for TestLoader.suiteClass that # It is implicit in the documentation for TestLoader.suiteClass that
...@@ -1278,7 +1302,7 @@ class Test_TestLoader(TestCase): ...@@ -1278,7 +1302,7 @@ class Test_TestLoader(TestCase):
def foo_bar(self): pass def foo_bar(self): pass
m.Foo = Foo m.Foo = Foo
tests = [[Foo('test_1'), Foo('test_2')]] tests = [unittest.ClassTestSuite([Foo('test_1'), Foo('test_2')], Foo)]
loader = unittest.TestLoader() loader = unittest.TestLoader()
loader.suiteClass = list loader.suiteClass = list
...@@ -2271,9 +2295,103 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): ...@@ -2271,9 +2295,103 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
# Make run() find a result object on its own # Make run() find a result object on its own
Foo('test').run() Foo('test').run()
expected = ['startTest', 'test', 'stopTest'] expected = ['startTest', 'test', 'addSuccess', 'stopTest']
self.assertEqual(events, expected) self.assertEqual(events, expected)
class Test_TestSkipping(TestCase):
def test_skipping(self):
class Foo(unittest.TestCase):
def test_skip_me(self):
self.skip("skip")
events = []
result = LoggingResult(events)
test = Foo("test_skip_me")
test.run(result)
self.assertEqual(events, ['startTest', 'addSkip', 'stopTest'])
self.assertEqual(result.skipped, [(test, "skip")])
# Try letting setUp skip the test now.
class Foo(unittest.TestCase):
def setUp(self):
self.skip("testing")
def test_nothing(self): pass
events = []
result = LoggingResult(events)
test = Foo("test_nothing")
test.run(result)
self.assertEqual(events, ['startTest', 'addSkip', 'stopTest'])
self.assertEqual(result.skipped, [(test, "testing")])
self.assertEqual(result.testsRun, 1)
def test_skipping_decorators(self):
op_table = ((unittest.skipUnless, False, True),
(unittest.skipIf, True, False))
for deco, do_skip, dont_skip in op_table:
class Foo(unittest.TestCase):
@deco(do_skip, "testing")
def test_skip(self): pass
@deco(dont_skip, "testing")
def test_dont_skip(self): pass
test_do_skip = Foo("test_skip")
test_dont_skip = Foo("test_dont_skip")
suite = unittest.ClassTestSuite([test_do_skip, test_dont_skip], Foo)
events = []
result = LoggingResult(events)
suite.run(result)
self.assertEqual(len(result.skipped), 1)
expected = ['startTest', 'addSkip', 'stopTest',
'startTest', 'addSuccess', 'stopTest']
self.assertEqual(events, expected)
self.assertEqual(result.testsRun, 2)
self.assertEqual(result.skipped, [(test_do_skip, "testing")])
self.assertTrue(result.wasSuccessful())
def test_skip_class(self):
@unittest.skip("testing")
class Foo(unittest.TestCase):
def test_1(self):
record.append(1)
record = []
result = unittest.TestResult()
suite = unittest.ClassTestSuite([Foo("test_1")], Foo)
suite.run(result)
self.assertEqual(result.skipped, [(suite, "testing")])
self.assertEqual(record, [])
def test_expected_failure(self):
class Foo(unittest.TestCase):
@unittest.expectedFailure
def test_die(self):
self.fail("help me!")
events = []
result = LoggingResult(events)
test = Foo("test_die")
test.run(result)
self.assertEqual(events,
['startTest', 'addExpectedFailure', 'stopTest'])
self.assertEqual(result.expected_failures[0][0], test)
self.assertTrue(result.wasSuccessful())
def test_unexpected_success(self):
class Foo(unittest.TestCase):
@unittest.expectedFailure
def test_die(self):
pass
events = []
result = LoggingResult(events)
test = Foo("test_die")
test.run(result)
self.assertEqual(events,
['startTest', 'addUnexpectedSuccess', 'stopTest'])
self.assertFalse(result.failures)
self.assertEqual(result.unexpected_successes, [test])
self.assertTrue(result.wasSuccessful())
class Test_Assertions(TestCase): class Test_Assertions(TestCase):
def test_AlmostEqual(self): def test_AlmostEqual(self):
self.failUnlessAlmostEqual(1.00000001, 1.0) self.failUnlessAlmostEqual(1.00000001, 1.0)
...@@ -2338,7 +2456,7 @@ class Test_Assertions(TestCase): ...@@ -2338,7 +2456,7 @@ class Test_Assertions(TestCase):
def test_main(): def test_main():
support.run_unittest(Test_TestCase, Test_TestLoader, support.run_unittest(Test_TestCase, Test_TestLoader,
Test_TestSuite, Test_TestResult, Test_FunctionTestCase, Test_TestSuite, Test_TestResult, Test_FunctionTestCase,
Test_Assertions) Test_TestSkipping, Test_Assertions)
if __name__ == "__main__": if __name__ == "__main__":
test_main() test_main()
...@@ -53,6 +53,7 @@ import sys ...@@ -53,6 +53,7 @@ import sys
import traceback import traceback
import os import os
import types import types
import functools
############################################################################## ##############################################################################
# Exported classes and functions # Exported classes and functions
...@@ -71,6 +72,79 @@ __all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases']) ...@@ -71,6 +72,79 @@ __all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
def _strclass(cls): def _strclass(cls):
return "%s.%s" % (cls.__module__, cls.__name__) return "%s.%s" % (cls.__module__, cls.__name__)
class SkipTest(Exception):
"""
Raise this exception in a test to skip it.
Usually you can use TestResult.skip() or one of the skipping decorators
instead of raising this directly.
"""
pass
class _ExpectedFailure(Exception):
"""
Raise this when a test is expected to fail.
This is an implementation detail.
"""
def __init__(self, exc_info):
super(_ExpectedFailure, self).__init__()
self.exc_info = exc_info
class _UnexpectedSuccess(Exception):
"""
The test was supposed to fail, but it didn't!
"""
pass
def _id(obj):
return obj
def skip(reason):
"""
Unconditionally skip a test.
"""
def decorator(test_item):
if isinstance(test_item, type) and issubclass(test_item, TestCase):
test_item.__unittest_skip__ = True
test_item.__unittest_skip_why__ = reason
return test_item
@functools.wraps(test_item)
def skip_wrapper(*args, **kwargs):
raise SkipTest(reason)
return skip_wrapper
return decorator
def skipIf(condition, reason):
"""
Skip a test if the condition is true.
"""
if condition:
return skip(reason)
return _id
def skipUnless(condition, reason):
"""
Skip a test unless the condition is true.
"""
if not condition:
return skip(reason)
return _id
def expectedFailure(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception:
raise _ExpectedFailure(sys.exc_info())
raise _UnexpectedSuccess
return wrapper
__unittest = 1 __unittest = 1
class TestResult(object): class TestResult(object):
...@@ -88,6 +162,9 @@ class TestResult(object): ...@@ -88,6 +162,9 @@ class TestResult(object):
self.failures = [] self.failures = []
self.errors = [] self.errors = []
self.testsRun = 0 self.testsRun = 0
self.skipped = []
self.expected_failures = []
self.unexpected_successes = []
self.shouldStop = False self.shouldStop = False
def startTest(self, test): def startTest(self, test):
...@@ -113,6 +190,19 @@ class TestResult(object): ...@@ -113,6 +190,19 @@ class TestResult(object):
"Called when a test has completed successfully" "Called when a test has completed successfully"
pass pass
def addSkip(self, test, reason):
"""Called when a test is skipped."""
self.skipped.append((test, reason))
def addExpectedFailure(self, test, err):
"""Called when an expected failure/error occured."""
self.expected_failures.append(
(test, self._exc_info_to_string(err, test)))
def addUnexpectedSuccess(self, test):
"""Called when a test was expected to fail, but succeed."""
self.unexpected_successes.append(test)
def wasSuccessful(self): def wasSuccessful(self):
"Tells whether or not this result was a success" "Tells whether or not this result was a success"
return len(self.failures) == len(self.errors) == 0 return len(self.failures) == len(self.errors) == 0
...@@ -273,25 +363,36 @@ class TestCase(object): ...@@ -273,25 +363,36 @@ class TestCase(object):
try: try:
try: try:
self.setUp() self.setUp()
except SkipTest as e:
result.addSkip(self, str(e))
return
except Exception: except Exception:
result.addError(self, self._exc_info()) result.addError(self, self._exc_info())
return return
ok = False success = False
try: try:
testMethod() testMethod()
ok = True
except self.failureException: except self.failureException:
result.addFailure(self, self._exc_info()) result.addFailure(self, self._exc_info())
except _ExpectedFailure as e:
result.addExpectedFailure(self, e.exc_info)
except _UnexpectedSuccess:
result.addUnexpectedSuccess(self)
except SkipTest as e:
result.addSkip(self, str(e))
except Exception: except Exception:
result.addError(self, self._exc_info()) result.addError(self, self._exc_info())
else:
success = True
try: try:
self.tearDown() self.tearDown()
except Exception: except Exception:
result.addError(self, self._exc_info()) result.addError(self, self._exc_info())
ok = False success = False
if ok: result.addSuccess(self) if success:
result.addSuccess(self)
finally: finally:
result.stopTest(self) result.stopTest(self)
...@@ -311,6 +412,10 @@ class TestCase(object): ...@@ -311,6 +412,10 @@ class TestCase(object):
""" """
return sys.exc_info() return sys.exc_info()
def skip(self, reason):
"""Skip this test."""
raise SkipTest(reason)
def fail(self, msg=None): def fail(self, msg=None):
"""Fail immediately, with the given message.""" """Fail immediately, with the given message."""
raise self.failureException(msg) raise self.failureException(msg)
...@@ -418,8 +523,8 @@ class TestSuite(object): ...@@ -418,8 +523,8 @@ class TestSuite(object):
__str__ = __repr__ __str__ = __repr__
def __eq__(self, other): def __eq__(self, other):
if type(self) is not type(other): if not isinstance(other, self.__class__):
return False return NotImplemented
return self._tests == other._tests return self._tests == other._tests
def __ne__(self, other): def __ne__(self, other):
...@@ -464,6 +569,37 @@ class TestSuite(object): ...@@ -464,6 +569,37 @@ class TestSuite(object):
for test in self._tests: test.debug() for test in self._tests: test.debug()
class ClassTestSuite(TestSuite):
"""
Suite of tests derived from a single TestCase class.
"""
def __init__(self, tests, class_collected_from):
super(ClassTestSuite, self).__init__(tests)
self.collected_from = class_collected_from
def id(self):
module = getattr(self.collected_from, "__module__", None)
if module is not None:
return "{0}.{1}".format(module, self.collected_from.__name__)
return self.collected_from.__name__
def run(self, result):
if getattr(self.collected_from, "__unittest_skip__", False):
# ClassTestSuite result pretends to be a TestCase enough to be
# reported.
result.startTest(self)
try:
result.addSkip(self, self.collected_from.__unittest_skip_why__)
finally:
result.stopTest(self)
else:
result = super(ClassTestSuite, self).run(result)
return result
shortDescription = id
class FunctionTestCase(TestCase): class FunctionTestCase(TestCase):
"""A test case that wraps a test function. """A test case that wraps a test function.
...@@ -550,6 +686,7 @@ class TestLoader(object): ...@@ -550,6 +686,7 @@ class TestLoader(object):
testMethodPrefix = 'test' testMethodPrefix = 'test'
sortTestMethodsUsing = staticmethod(three_way_cmp) sortTestMethodsUsing = staticmethod(three_way_cmp)
suiteClass = TestSuite suiteClass = TestSuite
classSuiteClass = ClassTestSuite
def loadTestsFromTestCase(self, testCaseClass): def loadTestsFromTestCase(self, testCaseClass):
"""Return a suite of all tests cases contained in testCaseClass""" """Return a suite of all tests cases contained in testCaseClass"""
...@@ -559,7 +696,9 @@ class TestLoader(object): ...@@ -559,7 +696,9 @@ class TestLoader(object):
testCaseNames = self.getTestCaseNames(testCaseClass) testCaseNames = self.getTestCaseNames(testCaseClass)
if not testCaseNames and hasattr(testCaseClass, 'runTest'): if not testCaseNames and hasattr(testCaseClass, 'runTest'):
testCaseNames = ['runTest'] testCaseNames = ['runTest']
return self.suiteClass(map(testCaseClass, testCaseNames)) suite = self.classSuiteClass(map(testCaseClass, testCaseNames),
testCaseClass)
return suite
def loadTestsFromModule(self, module): def loadTestsFromModule(self, module):
"""Return a suite of all tests cases contained in the given module""" """Return a suite of all tests cases contained in the given module"""
...@@ -739,6 +878,30 @@ class _TextTestResult(TestResult): ...@@ -739,6 +878,30 @@ class _TextTestResult(TestResult):
self.stream.write('F') self.stream.write('F')
self.stream.flush() self.stream.flush()
def addSkip(self, test, reason):
TestResult.addSkip(self, test, reason)
if self.showAll:
self.stream.writeln("skipped {0!r}".format(reason))
elif self.dots:
self.stream.write("s")
self.stream.flush()
def addExpectedFailure(self, test, err):
TestResult.addExpectedFailure(self, test, err)
if self.showAll:
self.stream.writeln("expected failure")
elif self.dots:
self.stream.write(".")
self.stream.flush()
def addUnexpectedSuccess(self, test):
TestResult.addUnexpectedSuccess(self, test)
if self.showAll:
self.stream.writeln("unexpected success")
elif self.dots:
self.stream.write(".")
self.stream.flush()
def printErrors(self): def printErrors(self):
if self.dots or self.showAll: if self.dots or self.showAll:
self.stream.writeln() self.stream.writeln()
...@@ -780,17 +943,28 @@ class TextTestRunner(object): ...@@ -780,17 +943,28 @@ class TextTestRunner(object):
self.stream.writeln("Ran %d test%s in %.3fs" % self.stream.writeln("Ran %d test%s in %.3fs" %
(run, run != 1 and "s" or "", timeTaken)) (run, run != 1 and "s" or "", timeTaken))
self.stream.writeln() self.stream.writeln()
results = map(len, (result.expected_failures,
result.unexpected_successes,
result.skipped))
expected_fails, unexpected_successes, skipped = results
infos = []
if not result.wasSuccessful(): if not result.wasSuccessful():
self.stream.write("FAILED (") self.stream.write("FAILED")
failed, errored = len(result.failures), len(result.errors) failed, errored = len(result.failures), len(result.errors)
if failed: if failed:
self.stream.write("failures=%d" % failed) infos.append("failures=%d" % failed)
if errored: if errored:
if failed: self.stream.write(", ") infos.append("errors=%d" % errored)
self.stream.write("errors=%d" % errored)
self.stream.writeln(")")
else: else:
self.stream.writeln("OK") self.stream.write("OK")
if skipped:
infos.append("skipped=%d" % skipped)
if expected_fails:
infos.append("expected failures=%d" % expected_fails)
if unexpected_successes:
infos.append("unexpected successes=%d" % unexpected_successes)
if infos:
self.stream.writeln(" (%s)" % (", ".join(infos),))
return result return result
...@@ -844,9 +1018,9 @@ Examples: ...@@ -844,9 +1018,9 @@ Examples:
def parseArgs(self, argv): def parseArgs(self, argv):
import getopt import getopt
long_opts = ['help','verbose','quiet']
try: try:
options, args = getopt.getopt(argv[1:], 'hHvq', options, args = getopt.getopt(argv[1:], 'hHvq', long_opts)
['help','verbose','quiet'])
for opt, value in options: for opt, value in options:
if opt in ('-h','-H','--help'): if opt in ('-h','-H','--help'):
self.usageExit() self.usageExit()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment