Adds an exit parameter to unittest.main(). If False main no longer

calls sys.exit.

Closes issue 3379.

Michael Foord
This commit is contained in:
Michael Foord 2009-05-02 11:43:06 +00:00
parent 27f204dc29
commit 829f6b8052
4 changed files with 98 additions and 12 deletions

View File

@ -1364,7 +1364,7 @@ Loading and running tests
subclasses to provide a custom ``TestResult``. subclasses to provide a custom ``TestResult``.
.. function:: main([module[, defaultTest[, argv[, testRunner[, testLoader]]]]]) .. function:: main([module[, defaultTest[, argv[, testRunner[, testLoader[, exit]]]]]])
A command-line program that runs a set of tests; this is primarily for making A command-line program that runs a set of tests; this is primarily for making
test modules conveniently executable. The simplest use for this function is to test modules conveniently executable. The simplest use for this function is to
@ -1374,4 +1374,18 @@ Loading and running tests
unittest.main() unittest.main()
The *testRunner* argument can either be a test runner class or an already The *testRunner* argument can either be a test runner class or an already
created instance of it. created instance of it. By default ``main`` calls :func:`sys.exit` with
an exit code indicating success or failure of the tests run.
``main`` supports being used from the interactive interpreter by passing in the
argument ``exit=False``. This displays the result on standard output without
calling :func:`sys.exit`::
>>> from unittest import main
>>> main(module='test_module', exit=False)
Calling ``main`` actually returns an instance of the ``TestProgram`` class.
This stores the result of the tests run as the ``result`` attribute.
.. versionchanged:: 2.7
The ``exit`` parameter was added.

View File

@ -477,6 +477,10 @@ changes, or look through the Subversion logs for all the details.
to provide additional information about why the two objects are to provide additional information about why the two objects are
matching, much as the new sequence comparison methods do. matching, much as the new sequence comparison methods do.
:func:`unittest.main` now takes an optional ``exit`` argument.
If False ``main`` doesn't call :func:`sys.exit` allowing it to
be used from the interactive interpreter. :issue:`3379`.
* The :func:`is_zipfile` function in the :mod:`zipfile` module will now * The :func:`is_zipfile` function in the :mod:`zipfile` module will now
accept a file object, in addition to the path names accepted in earlier accept a file object, in addition to the path names accepted in earlier
versions. (Contributed by Gabriel Genellina; :issue:`4756`.) versions. (Contributed by Gabriel Genellina; :issue:`4756`.)

View File

@ -9,9 +9,10 @@ Still need testing:
import re import re
from test import test_support from test import test_support
import unittest import unittest
from unittest import TestCase from unittest import TestCase, TestProgram
import types import types
from copy import deepcopy from copy import deepcopy
from cStringIO import StringIO
### Support code ### Support code
################################################################ ################################################################
@ -3040,6 +3041,73 @@ class TestLongMessage(TestCase):
"^unexpectedly identical: None : oops$"]) "^unexpectedly identical: None : oops$"])
class Test_TestProgram(TestCase):
# Horrible white box test
def testNoExit(self):
result = object()
test = object()
class FakeRunner(object):
def run(self, test):
self.test = test
return result
runner = FakeRunner()
try:
oldParseArgs = TestProgram.parseArgs
TestProgram.parseArgs = lambda *args: None
TestProgram.test = test
program = TestProgram(testRunner=runner, exit=False)
self.assertEqual(program.result, result)
self.assertEqual(runner.test, test)
finally:
TestProgram.parseArgs = oldParseArgs
del TestProgram.test
class FooBar(unittest.TestCase):
def testPass(self):
assert True
def testFail(self):
assert False
class FooBarLoader(unittest.TestLoader):
"""Test loader that returns a suite containing FooBar."""
def loadTestsFromModule(self, module):
return self.suiteClass(
[self.loadTestsFromTestCase(Test_TestProgram.FooBar)])
def test_NonExit(self):
program = unittest.main(exit=False,
testRunner=unittest.TextTestRunner(stream=StringIO()),
testLoader=self.FooBarLoader())
self.assertTrue(hasattr(program, 'result'))
def test_Exit(self):
self.assertRaises(
SystemExit,
unittest.main,
testRunner=unittest.TextTestRunner(stream=StringIO()),
exit=True,
testLoader=self.FooBarLoader())
def test_ExitAsDefault(self):
self.assertRaises(
SystemExit,
unittest.main,
testRunner=unittest.TextTestRunner(stream=StringIO()),
testLoader=self.FooBarLoader())
###################################################################### ######################################################################
## Main ## Main
###################################################################### ######################################################################
@ -3047,7 +3115,8 @@ class TestLongMessage(TestCase):
def test_main(): def test_main():
test_support.run_unittest(Test_TestCase, Test_TestLoader, test_support.run_unittest(Test_TestCase, Test_TestLoader,
Test_TestSuite, Test_TestResult, Test_FunctionTestCase, Test_TestSuite, Test_TestResult, Test_FunctionTestCase,
Test_TestSkipping, Test_Assertions, TestLongMessage) Test_TestSkipping, Test_Assertions, TestLongMessage,
Test_TestProgram)
if __name__ == "__main__": if __name__ == "__main__":
test_main() test_main()

View File

@ -1015,7 +1015,7 @@ class TestSuite(object):
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, self.__class__): if not isinstance(other, self.__class__):
return NotImplemented return NotImplemented
return self._tests == other._tests return list(self) == list(other)
def __ne__(self, other): def __ne__(self, other):
return not self == other return not self == other
@ -1469,7 +1469,7 @@ Examples:
""" """
def __init__(self, module='__main__', defaultTest=None, def __init__(self, module='__main__', defaultTest=None,
argv=None, testRunner=TextTestRunner, argv=None, testRunner=TextTestRunner,
testLoader=defaultTestLoader): testLoader=defaultTestLoader, exit=True):
if isinstance(module, basestring): if isinstance(module, basestring):
self.module = __import__(module) self.module = __import__(module)
for part in module.split('.')[1:]: for part in module.split('.')[1:]:
@ -1478,6 +1478,8 @@ Examples:
self.module = module self.module = module
if argv is None: if argv is None:
argv = sys.argv argv = sys.argv
self.exit = exit
self.verbosity = 1 self.verbosity = 1
self.defaultTest = defaultTest self.defaultTest = defaultTest
self.testRunner = testRunner self.testRunner = testRunner
@ -1529,15 +1531,12 @@ Examples:
else: else:
# it is assumed to be a TestRunner instance # it is assumed to be a TestRunner instance
testRunner = self.testRunner testRunner = self.testRunner
result = testRunner.run(self.test) self.result = testRunner.run(self.test)
sys.exit(not result.wasSuccessful()) if self.exit:
sys.exit(not self.result.wasSuccessful())
main = TestProgram main = TestProgram
##############################################################################
# Executing this module from the command line
##############################################################################
if __name__ == "__main__": if __name__ == "__main__":
main(module=None) main(module=None)