From 829f6b80529bcf66bf52bb9b301bf718118b8316 Mon Sep 17 00:00:00 2001 From: Michael Foord Date: Sat, 2 May 2009 11:43:06 +0000 Subject: Adds an exit parameter to unittest.main(). If False main no longer calls sys.exit. Closes issue 3379. Michael Foord --- Doc/library/unittest.rst | 18 ++++++++++-- Doc/whatsnew/2.7.rst | 4 +++ Lib/test/test_unittest.py | 73 +++++++++++++++++++++++++++++++++++++++++++++-- Lib/unittest.py | 15 +++++----- 4 files changed, 98 insertions(+), 12 deletions(-) diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 16da21c..065832d 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1364,7 +1364,7 @@ Loading and running tests 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 test modules conveniently executable. The simplest use for this function is to @@ -1374,4 +1374,18 @@ Loading and running tests unittest.main() 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. diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index 24d4549..92fedb7 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -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 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 accept a file object, in addition to the path names accepted in earlier versions. (Contributed by Gabriel Genellina; :issue:`4756`.) diff --git a/Lib/test/test_unittest.py b/Lib/test/test_unittest.py index f28b1af..bff5999 100644 --- a/Lib/test/test_unittest.py +++ b/Lib/test/test_unittest.py @@ -9,9 +9,10 @@ Still need testing: import re from test import test_support import unittest -from unittest import TestCase +from unittest import TestCase, TestProgram import types from copy import deepcopy +from cStringIO import StringIO ### Support code ################################################################ @@ -3040,6 +3041,73 @@ class TestLongMessage(TestCase): "^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 ###################################################################### @@ -3047,7 +3115,8 @@ class TestLongMessage(TestCase): def test_main(): test_support.run_unittest(Test_TestCase, Test_TestLoader, Test_TestSuite, Test_TestResult, Test_FunctionTestCase, - Test_TestSkipping, Test_Assertions, TestLongMessage) + Test_TestSkipping, Test_Assertions, TestLongMessage, + Test_TestProgram) if __name__ == "__main__": test_main() diff --git a/Lib/unittest.py b/Lib/unittest.py index 71d94cf..43a2ced 100644 --- a/Lib/unittest.py +++ b/Lib/unittest.py @@ -1015,7 +1015,7 @@ class TestSuite(object): def __eq__(self, other): if not isinstance(other, self.__class__): return NotImplemented - return self._tests == other._tests + return list(self) == list(other) def __ne__(self, other): return not self == other @@ -1469,7 +1469,7 @@ Examples: """ def __init__(self, module='__main__', defaultTest=None, argv=None, testRunner=TextTestRunner, - testLoader=defaultTestLoader): + testLoader=defaultTestLoader, exit=True): if isinstance(module, basestring): self.module = __import__(module) for part in module.split('.')[1:]: @@ -1478,6 +1478,8 @@ Examples: self.module = module if argv is None: argv = sys.argv + + self.exit = exit self.verbosity = 1 self.defaultTest = defaultTest self.testRunner = testRunner @@ -1529,15 +1531,12 @@ Examples: else: # it is assumed to be a TestRunner instance testRunner = self.testRunner - result = testRunner.run(self.test) - sys.exit(not result.wasSuccessful()) + self.result = testRunner.run(self.test) + if self.exit: + sys.exit(not self.result.wasSuccessful()) main = TestProgram -############################################################################## -# Executing this module from the command line -############################################################################## - if __name__ == "__main__": main(module=None) -- cgit v0.12