summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Peterson <benjamin@python.org>2010-03-14 15:04:17 +0000
committerBenjamin Peterson <benjamin@python.org>2010-03-14 15:04:17 +0000
commitffee44ba935c4752b965ee51b5d61936b77258ac (patch)
tree70aa757b0916c7e1c8997c1fea0438bf8da545f0
parent7ff86796e67b026b5ff0343b7f8830960ea0f111 (diff)
downloadcpython-ffee44ba935c4752b965ee51b5d61936b77258ac.tar.gz
Merged revisions 78227,78229,78288,78348,78377,78770,78774-78776,78810 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk ........ r78227 | michael.foord | 2010-02-18 14:30:09 -0600 (Thu, 18 Feb 2010) | 1 line unittest.TestCase uses safe_repr for producing failure messages. Partial fix for issue 7956 ........ r78229 | michael.foord | 2010-02-18 15:37:07 -0600 (Thu, 18 Feb 2010) | 1 line Fix unittest.TestCase.assertDictContainsSubset so it can't die with unicode issues when constructing failure messages. Issue 7956 ........ r78288 | michael.foord | 2010-02-21 08:48:59 -0600 (Sun, 21 Feb 2010) | 1 line Silence UnicodeWarning in crazy unittest test. ........ r78348 | michael.foord | 2010-02-22 17:28:32 -0600 (Mon, 22 Feb 2010) | 1 line Support for old TestResult object (unittest) with warnings when using unsupported features. ........ r78377 | michael.foord | 2010-02-23 11:00:53 -0600 (Tue, 23 Feb 2010) | 1 line unittest.TestResult can now be used with the TextTestRunner. TextTestRunner compatible with old TestResult objects. ........ r78770 | michael.foord | 2010-03-07 14:22:12 -0600 (Sun, 07 Mar 2010) | 1 line Fix for potentials errors in constructing unittest failure messages. Plus skipped test methods no longer run setUp and tearDown (Issue 8059) ........ r78774 | michael.foord | 2010-03-07 16:04:55 -0600 (Sun, 07 Mar 2010) | 1 line Addition of setUpClass and setUpModule shared fixtures to unittest. ........ r78775 | michael.foord | 2010-03-07 17:10:36 -0600 (Sun, 07 Mar 2010) | 1 line Fix accidental name rebinding in unittest py3k warning filtering. ........ r78776 | michael.foord | 2010-03-07 17:16:20 -0600 (Sun, 07 Mar 2010) | 1 line Remove accidental print statement from last commit. ........ r78810 | raymond.hettinger | 2010-03-09 02:44:18 -0600 (Tue, 09 Mar 2010) | 5 lines Improve the basic example. * Show both the decorator and regular form for assertRaises() * Use assertTrue() instead of assertIn() to teach useful minimal subset of the API ........
-rw-r--r--Doc/library/unittest.rst10
-rw-r--r--Lib/test/test_unittest.py540
-rw-r--r--Lib/unittest/__init__.py3
-rw-r--r--Lib/unittest/case.py173
-rw-r--r--Lib/unittest/result.py11
-rw-r--r--Lib/unittest/runner.py17
-rw-r--r--Lib/unittest/suite.py199
-rw-r--r--Lib/unittest/util.py6
8 files changed, 865 insertions, 94 deletions
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst
index e2272f7146..4671f8a2d7 100644
--- a/Doc/library/unittest.rst
+++ b/Doc/library/unittest.rst
@@ -177,14 +177,18 @@ Here is a short script to test three functions from the :mod:`random` module::
self.seq.sort()
self.assertEqual(self.seq, list(range(10)))
+ # should raise an exception for an immutable sequence
+ self.assertRaises(TypeError, random.shuffle, (1,2,3))
+
def test_choice(self):
element = random.choice(self.seq)
- self.assertIn(element, self.seq)
+ self.assertTrue(element in self.seq)
def test_sample(self):
- self.assertRaises(ValueError, random.sample, self.seq, 20)
+ with self.assertRaises(ValueError):
+ random.sample(self.seq, 20)
for element in random.sample(self.seq, 5):
- self.assertIn(element, self.seq)
+ self.assertTrue(element in self.seq)
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_unittest.py b/Lib/test/test_unittest.py
index 75b9d0c797..453c48d6e8 100644
--- a/Lib/test/test_unittest.py
+++ b/Lib/test/test_unittest.py
@@ -18,10 +18,15 @@ import types
from copy import deepcopy
import io
import pickle
+import warnings
+
### Support code
################################################################
+def resultFactory(*_):
+ return unittest.TestResult()
+
class LoggingResult(unittest.TestResult):
def __init__(self, log):
self._events = log
@@ -2076,6 +2081,70 @@ class Test_TestResult(TestCase):
'Tests getDescription() for a method with a longer '
'docstring.'))
+classDict = dict(unittest.TestResult.__dict__)
+for m in ('addSkip', 'addExpectedFailure', 'addUnexpectedSuccess',
+ '__init__'):
+ del classDict[m]
+
+def __init__(self, stream=None, descriptions=None, verbosity=None):
+ self.failures = []
+ self.errors = []
+ self.testsRun = 0
+ self.shouldStop = False
+classDict['__init__'] = __init__
+OldResult = type('OldResult', (object,), classDict)
+
+class Test_OldTestResult(unittest.TestCase):
+
+ def assertOldResultWarning(self, test, failures):
+ with warnings.catch_warnings(record=True) as log:
+ result = OldResult()
+ test.run(result)
+ self.assertEqual(len(result.failures), failures)
+ warning, = log
+ self.assertIs(warning.category, RuntimeWarning)
+
+ def testOldTestResult(self):
+ class Test(unittest.TestCase):
+ def testSkip(self):
+ self.skipTest('foobar')
+ @unittest.expectedFailure
+ def testExpectedFail(self):
+ raise TypeError
+ @unittest.expectedFailure
+ def testUnexpectedSuccess(self):
+ pass
+
+ for test_name, should_pass in (('testSkip', True),
+ ('testExpectedFail', True),
+ ('testUnexpectedSuccess', False)):
+ test = Test(test_name)
+ self.assertOldResultWarning(test, int(not should_pass))
+
+ def testOldTestTesultSetup(self):
+ class Test(unittest.TestCase):
+ def setUp(self):
+ self.skipTest('no reason')
+ def testFoo(self):
+ pass
+ self.assertOldResultWarning(Test('testFoo'), 0)
+
+ def testOldTestResultClass(self):
+ @unittest.skip('no reason')
+ class Test(unittest.TestCase):
+ def testFoo(self):
+ pass
+ self.assertOldResultWarning(Test('testFoo'), 0)
+
+ def testOldResultWithRunner(self):
+ class Test(unittest.TestCase):
+ def testFoo(self):
+ pass
+ runner = unittest.TextTestRunner(resultclass=OldResult,
+ stream=io.StringIO())
+ # This will raise an exception if TextTestRunner can't handle old
+ # test result objects
+ runner.run(Test('testFoo'))
### Support code for Test_TestCase
################################################################
@@ -2578,21 +2647,27 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
self.assertDictContainsSubset({'a': 1}, {'a': 1, 'b': 2})
self.assertDictContainsSubset({'a': 1, 'b': 2}, {'a': 1, 'b': 2})
- self.assertRaises(unittest.TestCase.failureException,
- self.assertDictContainsSubset, {'a': 2}, {'a': 1},
- '.*Mismatched values:.*')
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({1: "one"}, {})
- self.assertRaises(unittest.TestCase.failureException,
- self.assertDictContainsSubset, {'c': 1}, {'a': 1},
- '.*Missing:.*')
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({'a': 2}, {'a': 1})
- self.assertRaises(unittest.TestCase.failureException,
- self.assertDictContainsSubset, {'a': 1, 'c': 1},
- {'a': 1}, '.*Missing:.*')
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({'c': 1}, {'a': 1})
- self.assertRaises(unittest.TestCase.failureException,
- self.assertDictContainsSubset, {'a': 1, 'c': 1},
- {'a': 1}, '.*Missing:.*Mismatched values:.*')
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({'a': 1, 'c': 1}, {'a': 1})
+
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({'a': 1, 'c': 1}, {'a': 1})
+
+ with warnings.catch_warnings(record=True):
+ # silence the UnicodeWarning
+ one = ''.join(chr(i) for i in range(255))
+ # this used to cause a UnicodeDecodeError constructing the failure msg
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({'foo': one}, {'foo': '\uFFFD'})
def testAssertEqual(self):
equal_pairs = [
@@ -3028,6 +3103,43 @@ class Test_TestSkipping(TestCase):
self.assertEqual(result.unexpectedSuccesses, [test])
self.assertTrue(result.wasSuccessful())
+ def test_skip_doesnt_run_setup(self):
+ class Foo(unittest.TestCase):
+ wasSetUp = False
+ wasTornDown = False
+ def setUp(self):
+ Foo.wasSetUp = True
+ def tornDown(self):
+ Foo.wasTornDown = True
+ @unittest.skip('testing')
+ def test_1(self):
+ pass
+
+ result = unittest.TestResult()
+ test = Foo("test_1")
+ suite = unittest.TestSuite([test])
+ suite.run(result)
+ self.assertEqual(result.skipped, [(test, "testing")])
+ self.assertFalse(Foo.wasSetUp)
+ self.assertFalse(Foo.wasTornDown)
+
+ def test_decorated_skip(self):
+ def decorator(func):
+ def inner(*a):
+ return func(*a)
+ return inner
+
+ class Foo(unittest.TestCase):
+ @decorator
+ @unittest.skip('testing')
+ def test_1(self):
+ pass
+
+ result = unittest.TestResult()
+ test = Foo("test_1")
+ suite = unittest.TestSuite([test])
+ suite.run(result)
+ self.assertEqual(result.skipped, [(test, "testing")])
class Test_Assertions(TestCase):
@@ -3131,6 +3243,16 @@ class TestLongMessage(TestCase):
self.assertEquals(self.testableTrue._formatMessage(None, "foo"), "foo")
self.assertEquals(self.testableTrue._formatMessage("foo", "bar"), "bar : foo")
+ # This blows up if _formatMessage uses string concatenation
+ self.testableTrue._formatMessage(object(), 'foo')
+
+ def test_formatMessage_unicode_error(self):
+ with warnings.catch_warnings(record=True):
+ # This causes a UnicodeWarning due to its craziness
+ one = ''.join(chr(i) for i in range(255))
+ # this used to cause a UnicodeDecodeError constructing msg
+ self.testableTrue._formatMessage(one, '\uFFFD')
+
def assertMessages(self, methodName, args, errors):
def getMethod(i):
useTestableFalse = i < 2
@@ -3795,6 +3917,397 @@ class TestDiscovery(TestCase):
self.assertEqual(program.verbosity, 2)
+class TestSetups(unittest.TestCase):
+
+ def getRunner(self):
+ return unittest.TextTestRunner(resultclass=resultFactory,
+ stream=io.StringIO())
+ def runTests(self, *cases):
+ suite = unittest.TestSuite()
+ for case in cases:
+ tests = unittest.defaultTestLoader.loadTestsFromTestCase(case)
+ suite.addTests(tests)
+
+ runner = self.getRunner()
+
+ # creating a nested suite exposes some potential bugs
+ realSuite = unittest.TestSuite()
+ realSuite.addTest(suite)
+ # adding empty suites to the end exposes potential bugs
+ suite.addTest(unittest.TestSuite())
+ realSuite.addTest(unittest.TestSuite())
+ return runner.run(realSuite)
+
+ def test_setup_class(self):
+ class Test(unittest.TestCase):
+ setUpCalled = 0
+ @classmethod
+ def setUpClass(cls):
+ Test.setUpCalled += 1
+ unittest.TestCase.setUpClass()
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+
+ result = self.runTests(Test)
+
+ self.assertEqual(Test.setUpCalled, 1)
+ self.assertEqual(result.testsRun, 2)
+ self.assertEqual(len(result.errors), 0)
+
+ def test_teardown_class(self):
+ class Test(unittest.TestCase):
+ tearDownCalled = 0
+ @classmethod
+ def tearDownClass(cls):
+ Test.tearDownCalled += 1
+ unittest.TestCase.tearDownClass()
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+
+ result = self.runTests(Test)
+
+ self.assertEqual(Test.tearDownCalled, 1)
+ self.assertEqual(result.testsRun, 2)
+ self.assertEqual(len(result.errors), 0)
+
+ def test_teardown_class_two_classes(self):
+ class Test(unittest.TestCase):
+ tearDownCalled = 0
+ @classmethod
+ def tearDownClass(cls):
+ Test.tearDownCalled += 1
+ unittest.TestCase.tearDownClass()
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+
+ class Test2(unittest.TestCase):
+ tearDownCalled = 0
+ @classmethod
+ def tearDownClass(cls):
+ Test2.tearDownCalled += 1
+ unittest.TestCase.tearDownClass()
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+
+ result = self.runTests(Test, Test2)
+
+ self.assertEqual(Test.tearDownCalled, 1)
+ self.assertEqual(Test2.tearDownCalled, 1)
+ self.assertEqual(result.testsRun, 4)
+ self.assertEqual(len(result.errors), 0)
+
+ def test_error_in_setupclass(self):
+ class BrokenTest(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ raise TypeError('foo')
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+
+ result = self.runTests(BrokenTest)
+
+ self.assertEqual(result.testsRun, 0)
+ self.assertEqual(len(result.errors), 1)
+ error, _ = result.errors[0]
+ self.assertEqual(str(error),
+ 'classSetUp (%s.BrokenTest)' % __name__)
+
+ def test_error_in_teardown_class(self):
+ class Test(unittest.TestCase):
+ tornDown = 0
+ @classmethod
+ def tearDownClass(cls):
+ Test.tornDown += 1
+ raise TypeError('foo')
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+
+ class Test2(unittest.TestCase):
+ tornDown = 0
+ @classmethod
+ def tearDownClass(cls):
+ Test2.tornDown += 1
+ raise TypeError('foo')
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+
+ result = self.runTests(Test, Test2)
+ self.assertEqual(result.testsRun, 4)
+ self.assertEqual(len(result.errors), 2)
+ self.assertEqual(Test.tornDown, 1)
+ self.assertEqual(Test2.tornDown, 1)
+
+ error, _ = result.errors[0]
+ self.assertEqual(str(error),
+ 'classTearDown (%s.Test)' % __name__)
+
+ def test_class_not_torndown_when_setup_fails(self):
+ class Test(unittest.TestCase):
+ tornDown = False
+ @classmethod
+ def setUpClass(cls):
+ raise TypeError
+ @classmethod
+ def tearDownClass(cls):
+ Test.tornDown = True
+ raise TypeError('foo')
+ def test_one(self):
+ pass
+
+ self.runTests(Test)
+ self.assertFalse(Test.tornDown)
+
+ def test_class_not_setup_or_torndown_when_skipped(self):
+ class Test(unittest.TestCase):
+ classSetUp = False
+ tornDown = False
+ @classmethod
+ def setUpClass(cls):
+ Test.classSetUp = True
+ @classmethod
+ def tearDownClass(cls):
+ Test.tornDown = True
+ def test_one(self):
+ pass
+
+ Test = unittest.skip("hop")(Test)
+ self.runTests(Test)
+ self.assertFalse(Test.classSetUp)
+ self.assertFalse(Test.tornDown)
+
+ def test_setup_teardown_order_with_pathological_suite(self):
+ results = []
+
+ class Module1(object):
+ @staticmethod
+ def setUpModule():
+ results.append('Module1.setUpModule')
+ @staticmethod
+ def tearDownModule():
+ results.append('Module1.tearDownModule')
+
+ class Module2(object):
+ @staticmethod
+ def setUpModule():
+ results.append('Module2.setUpModule')
+ @staticmethod
+ def tearDownModule():
+ results.append('Module2.tearDownModule')
+
+ class Test1(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ results.append('setup 1')
+ @classmethod
+ def tearDownClass(cls):
+ results.append('teardown 1')
+ def testOne(self):
+ results.append('Test1.testOne')
+ def testTwo(self):
+ results.append('Test1.testTwo')
+
+ class Test2(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ results.append('setup 2')
+ @classmethod
+ def tearDownClass(cls):
+ results.append('teardown 2')
+ def testOne(self):
+ results.append('Test2.testOne')
+ def testTwo(self):
+ results.append('Test2.testTwo')
+
+ class Test3(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ results.append('setup 3')
+ @classmethod
+ def tearDownClass(cls):
+ results.append('teardown 3')
+ def testOne(self):
+ results.append('Test3.testOne')
+ def testTwo(self):
+ results.append('Test3.testTwo')
+
+ Test1.__module__ = Test2.__module__ = 'Module'
+ Test3.__module__ = 'Module2'
+ sys.modules['Module'] = Module1
+ sys.modules['Module2'] = Module2
+
+ first = unittest.TestSuite((Test1('testOne'),))
+ second = unittest.TestSuite((Test1('testTwo'),))
+ third = unittest.TestSuite((Test2('testOne'),))
+ fourth = unittest.TestSuite((Test2('testTwo'),))
+ fifth = unittest.TestSuite((Test3('testOne'),))
+ sixth = unittest.TestSuite((Test3('testTwo'),))
+ suite = unittest.TestSuite((first, second, third, fourth, fifth, sixth))
+
+ runner = self.getRunner()
+ result = runner.run(suite)
+ self.assertEqual(result.testsRun, 6)
+ self.assertEqual(len(result.errors), 0)
+
+ self.assertEqual(results,
+ ['Module1.setUpModule', 'setup 1',
+ 'Test1.testOne', 'Test1.testTwo', 'teardown 1',
+ 'setup 2', 'Test2.testOne', 'Test2.testTwo',
+ 'teardown 2', 'Module1.tearDownModule',
+ 'Module2.setUpModule', 'setup 3',
+ 'Test3.testOne', 'Test3.testTwo',
+ 'teardown 3', 'Module2.tearDownModule'])
+
+ def test_setup_module(self):
+ class Module(object):
+ moduleSetup = 0
+ @staticmethod
+ def setUpModule():
+ Module.moduleSetup += 1
+
+ class Test(unittest.TestCase):
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+ Test.__module__ = 'Module'
+ sys.modules['Module'] = Module
+
+ result = self.runTests(Test)
+ self.assertEqual(Module.moduleSetup, 1)
+ self.assertEqual(result.testsRun, 2)
+ self.assertEqual(len(result.errors), 0)
+
+ def test_error_in_setup_module(self):
+ class Module(object):
+ moduleSetup = 0
+ moduleTornDown = 0
+ @staticmethod
+ def setUpModule():
+ Module.moduleSetup += 1
+ raise TypeError('foo')
+ @staticmethod
+ def tearDownModule():
+ Module.moduleTornDown += 1
+
+ class Test(unittest.TestCase):
+ classSetUp = False
+ classTornDown = False
+ @classmethod
+ def setUpClass(cls):
+ Test.classSetUp = True
+ @classmethod
+ def tearDownClass(cls):
+ Test.classTornDown = True
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+
+ class Test2(unittest.TestCase):
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+ Test.__module__ = 'Module'
+ Test2.__module__ = 'Module'
+ sys.modules['Module'] = Module
+
+ result = self.runTests(Test, Test2)
+ self.assertEqual(Module.moduleSetup, 1)
+ self.assertEqual(Module.moduleTornDown, 0)
+ self.assertEqual(result.testsRun, 0)
+ self.assertFalse(Test.classSetUp)
+ self.assertFalse(Test.classTornDown)
+ self.assertEqual(len(result.errors), 1)
+ error, _ = result.errors[0]
+ self.assertEqual(str(error), 'setUpModule (Module)')
+
+ def test_testcase_with_missing_module(self):
+ class Test(unittest.TestCase):
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+ Test.__module__ = 'Module'
+ sys.modules.pop('Module', None)
+
+ result = self.runTests(Test)
+ self.assertEqual(result.testsRun, 2)
+
+ def test_teardown_module(self):
+ class Module(object):
+ moduleTornDown = 0
+ @staticmethod
+ def tearDownModule():
+ Module.moduleTornDown += 1
+
+ class Test(unittest.TestCase):
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+ Test.__module__ = 'Module'
+ sys.modules['Module'] = Module
+
+ result = self.runTests(Test)
+ self.assertEqual(Module.moduleTornDown, 1)
+ self.assertEqual(result.testsRun, 2)
+ self.assertEqual(len(result.errors), 0)
+
+ def test_error_in_teardown_module(self):
+ class Module(object):
+ moduleTornDown = 0
+ @staticmethod
+ def tearDownModule():
+ Module.moduleTornDown += 1
+ raise TypeError('foo')
+
+ class Test(unittest.TestCase):
+ classSetUp = False
+ classTornDown = False
+ @classmethod
+ def setUpClass(cls):
+ Test.classSetUp = True
+ @classmethod
+ def tearDownClass(cls):
+ Test.classTornDown = True
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+
+ class Test2(unittest.TestCase):
+ def test_one(self):
+ pass
+ def test_two(self):
+ pass
+ Test.__module__ = 'Module'
+ Test2.__module__ = 'Module'
+ sys.modules['Module'] = Module
+
+ result = self.runTests(Test, Test2)
+ self.assertEqual(Module.moduleTornDown, 1)
+ self.assertEqual(result.testsRun, 4)
+ self.assertTrue(Test.classSetUp)
+ self.assertTrue(Test.classTornDown)
+ self.assertEqual(len(result.errors), 1)
+ error, _ = result.errors[0]
+ self.assertEqual(str(error), 'tearDownModule (Module)')
+
######################################################################
## Main
######################################################################
@@ -3803,7 +4316,8 @@ def test_main():
support.run_unittest(Test_TestCase, Test_TestLoader,
Test_TestSuite, Test_TestResult, Test_FunctionTestCase,
Test_TestSkipping, Test_Assertions, TestLongMessage,
- Test_TestProgram, TestCleanUp, TestDiscovery, Test_TextTestRunner)
+ Test_TestProgram, TestCleanUp, TestDiscovery, Test_TextTestRunner,
+ Test_OldTestResult, TestSetups)
if __name__ == "__main__":
test_main()
diff --git a/Lib/unittest/__init__.py b/Lib/unittest/__init__.py
index 4a308fa1c4..06fe55d08d 100644
--- a/Lib/unittest/__init__.py
+++ b/Lib/unittest/__init__.py
@@ -51,13 +51,12 @@ __all__ = ['TestResult', 'TestCase', 'TestSuite',
# Expose obsolete functions for backwards compatibility
__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
-__all__.append('_TextTestResult')
from .result import TestResult
from .case import (TestCase, FunctionTestCase, SkipTest, skip, skipIf,
skipUnless, expectedFailure)
-from .suite import TestSuite
+from .suite import BaseTestSuite, TestSuite
from .loader import (TestLoader, defaultTestLoader, makeSuite, getTestCaseNames,
findTestCases)
from .main import TestProgram, main
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index cc121e54a0..8900915979 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -7,7 +7,9 @@ import pprint
import re
import warnings
-from . import result, util
+from . import result
+from .util import (strclass, safe_repr, sorted_list_difference,
+ unorderable_list_difference)
class SkipTest(Exception):
@@ -44,14 +46,15 @@ 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
+ if not (isinstance(test_item, type) and issubclass(test_item, TestCase)):
+ @functools.wraps(test_item)
+ def skip_wrapper(*args, **kwargs):
+ raise SkipTest(reason)
+ test_item = skip_wrapper
+
+ test_item.__unittest_skip__ = True
+ test_item.__unittest_skip_why__ = reason
+ return test_item
return decorator
def skipIf(condition, reason):
@@ -164,6 +167,9 @@ class TestCase(object):
longMessage = False
+ # Attribute used by TestSuite for classSetUp
+
+ _classSetupFailed = False
def __init__(self, methodName='runTest'):
"""Create an instance of the class that will use the named test
@@ -175,7 +181,7 @@ class TestCase(object):
try:
testMethod = getattr(self, methodName)
except AttributeError:
- raise ValueError("no such test method in %s: %s" % \
+ raise ValueError("no such test method in %s: %s" %
(self.__class__, methodName))
self._testMethodDoc = testMethod.__doc__
self._cleanups = []
@@ -222,6 +228,14 @@ class TestCase(object):
"Hook method for deconstructing the test fixture after testing it."
pass
+ @classmethod
+ def setUpClass(cls):
+ "Hook method for setting up class fixture before running tests in the class."
+
+ @classmethod
+ def tearDownClass(cls):
+ "Hook method for deconstructing the class fixture after running all tests in the class."
+
def countTestCases(self):
return 1
@@ -240,7 +254,7 @@ class TestCase(object):
def id(self):
- return "%s.%s" % (util.strclass(self.__class__), self._testMethodName)
+ return "%s.%s" % (strclass(self.__class__), self._testMethodName)
def __eq__(self, other):
if type(self) is not type(other):
@@ -255,11 +269,20 @@ class TestCase(object):
return hash((type(self), self._testMethodName))
def __str__(self):
- return "%s (%s)" % (self._testMethodName, util.strclass(self.__class__))
+ return "%s (%s)" % (self._testMethodName, strclass(self.__class__))
def __repr__(self):
return "<%s testMethod=%s>" % \
- (util.strclass(self.__class__), self._testMethodName)
+ (strclass(self.__class__), self._testMethodName)
+
+ def _addSkip(self, result, reason):
+ addSkip = getattr(result, 'addSkip', None)
+ if addSkip is not None:
+ addSkip(self, reason)
+ else:
+ warnings.warn("TestResult has no addSkip method, skips not reported",
+ RuntimeWarning, 2)
+ result.addSuccess(self)
def run(self, result=None):
orig_result = result
@@ -271,20 +294,24 @@ class TestCase(object):
self._resultForDoCleanups = result
result.startTest(self)
- if getattr(self.__class__, "__unittest_skip__", False):
- # If the whole class was skipped.
+
+ testMethod = getattr(self, self._testMethodName)
+ if (getattr(self.__class__, "__unittest_skip__", False) or
+ getattr(testMethod, "__unittest_skip__", False)):
+ # If the class or method was skipped.
try:
- result.addSkip(self, self.__class__.__unittest_skip_why__)
+ skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
+ or getattr(testMethod, '__unittest_skip_why__', ''))
+ self._addSkip(result, skip_why)
finally:
result.stopTest(self)
return
- testMethod = getattr(self, self._testMethodName)
try:
success = False
try:
self.setUp()
except SkipTest as e:
- result.addSkip(self, str(e))
+ self._addSkip(result, str(e))
except Exception:
result.addError(self, sys.exc_info())
else:
@@ -293,11 +320,23 @@ class TestCase(object):
except self.failureException:
result.addFailure(self, sys.exc_info())
except _ExpectedFailure as e:
- result.addExpectedFailure(self, e.exc_info)
+ addExpectedFailure = getattr(result, 'addExpectedFailure', None)
+ if addExpectedFailure is not None:
+ addExpectedFailure(self, e.exc_info)
+ else:
+ warnings.warn("TestResult has no addExpectedFailure method, reporting as passes",
+ RuntimeWarning)
+ result.addSuccess(self)
except _UnexpectedSuccess:
- result.addUnexpectedSuccess(self)
+ addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
+ if addUnexpectedSuccess is not None:
+ addUnexpectedSuccess(self)
+ else:
+ warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures",
+ RuntimeWarning)
+ result.addFailure(self, sys.exc_info())
except SkipTest as e:
- result.addSkip(self, str(e))
+ self._addSkip(result, str(e))
except Exception:
result.addError(self, sys.exc_info())
else:
@@ -354,13 +393,13 @@ class TestCase(object):
def assertFalse(self, expr, msg=None):
"Fail the test if the expression is true."
if expr:
- msg = self._formatMessage(msg, "%r is not False" % expr)
+ msg = self._formatMessage(msg, "%s is not False" % safe_repr(expr))
raise self.failureException(msg)
def assertTrue(self, expr, msg=None):
"""Fail the test unless the expression is true."""
if not expr:
- msg = self._formatMessage(msg, "%r is not True" % expr)
+ msg = self._formatMessage(msg, "%s is not True" % safe_repr(expr))
raise self.failureException(msg)
def _formatMessage(self, msg, standardMsg):
@@ -377,7 +416,12 @@ class TestCase(object):
return msg or standardMsg
if msg is None:
return standardMsg
- return standardMsg + ' : ' + msg
+ try:
+ # don't switch to '{}' formatting in Python 2.X
+ # it changes the way unicode input is handled
+ return '%s : %s' % (standardMsg, msg)
+ except UnicodeDecodeError:
+ return '%s : %s' % (safe_repr(standardMsg), safe_repr(msg))
def assertRaises(self, excClass, callableObj=None, *args, **kwargs):
@@ -436,7 +480,7 @@ class TestCase(object):
def _baseAssertEqual(self, first, second, msg=None):
"""The default assertEqual implementation, not type specific."""
if not first == second:
- standardMsg = '%r != %r' % (first, second)
+ standardMsg = '%s != %s' % (safe_repr(first), safe_repr(second))
msg = self._formatMessage(msg, standardMsg)
raise self.failureException(msg)
@@ -452,7 +496,8 @@ class TestCase(object):
operator.
"""
if not first != second:
- msg = self._formatMessage(msg, '%r == %r' % (first, second))
+ msg = self._formatMessage(msg, '%s == %s' % (safe_repr(first),
+ safe_repr(second)))
raise self.failureException(msg)
def assertAlmostEqual(self, first, second, *, places=7, msg=None):
@@ -467,10 +512,12 @@ class TestCase(object):
compare almost equal.
"""
if first == second:
- # shortcut for ite
+ # shortcut for inf
return
if round(abs(second-first), places) != 0:
- standardMsg = '%r != %r within %r places' % (first, second, places)
+ standardMsg = '%s != %s within %r places' % (safe_repr(first),
+ safe_repr(second),
+ places)
msg = self._formatMessage(msg, standardMsg)
raise self.failureException(msg)
@@ -485,7 +532,9 @@ class TestCase(object):
Objects that are equal automatically fail.
"""
if (first == second) or round(abs(second-first), places) == 0:
- standardMsg = '%r == %r within %r places' % (first, second, places)
+ standardMsg = '%s == %s within %r places' % (safe_repr(first),
+ safe_repr(second),
+ places)
msg = self._formatMessage(msg, standardMsg)
raise self.failureException(msg)
@@ -535,11 +584,11 @@ class TestCase(object):
if seq_type != None:
seq_type_name = seq_type.__name__
if not isinstance(seq1, seq_type):
- raise self.failureException('First sequence is not a %s: %r'
- % (seq_type_name, seq1))
+ raise self.failureException('First sequence is not a %s: %s'
+ % (seq_type_name, safe_repr(seq1)))
if not isinstance(seq2, seq_type):
- raise self.failureException('Second sequence is not a %s: %r'
- % (seq_type_name, seq2))
+ raise self.failureException('Second sequence is not a %s: %s'
+ % (seq_type_name, safe_repr(seq2)))
else:
seq_type_name = "sequence"
@@ -561,8 +610,8 @@ class TestCase(object):
if seq1 == seq2:
return
- seq1_repr = repr(seq1)
- seq2_repr = repr(seq2)
+ seq1_repr = safe_repr(seq1)
+ seq2_repr = safe_repr(seq2)
if len(seq1_repr) > 30:
seq1_repr = seq1_repr[:30] + '...'
if len(seq2_repr) > 30:
@@ -689,25 +738,28 @@ class TestCase(object):
def assertIn(self, member, container, msg=None):
"""Just like self.assertTrue(a in b), but with a nicer default message."""
if member not in container:
- standardMsg = '%r not found in %r' % (member, container)
+ standardMsg = '%s not found in %s' % (safe_repr(member),
+ safe_repr(container))
self.fail(self._formatMessage(msg, standardMsg))
def assertNotIn(self, member, container, msg=None):
"""Just like self.assertTrue(a not in b), but with a nicer default message."""
if member in container:
- standardMsg = '%r unexpectedly found in %r' % (member, container)
+ standardMsg = '%s unexpectedly found in %s' % (safe_repr(member),
+ safe_repr(container))
self.fail(self._formatMessage(msg, standardMsg))
def assertIs(self, expr1, expr2, msg=None):
"""Just like self.assertTrue(a is b), but with a nicer default message."""
if expr1 is not expr2:
- standardMsg = '%r is not %r' % (expr1, expr2)
+ standardMsg = '%s is not %s' % (safe_repr(expr1),
+ safe_repr(expr2))
self.fail(self._formatMessage(msg, standardMsg))
def assertIsNot(self, expr1, expr2, msg=None):
"""Just like self.assertTrue(a is not b), but with a nicer default message."""
if expr1 is expr2:
- standardMsg = 'unexpectedly identical: %r' % (expr1,)
+ standardMsg = 'unexpectedly identical: %s' % (safe_repr(expr1),)
self.fail(self._formatMessage(msg, standardMsg))
def assertDictEqual(self, d1, d2, msg=None):
@@ -729,14 +781,16 @@ class TestCase(object):
missing.append(key)
elif value != actual[key]:
mismatched.append('%s, expected: %s, actual: %s' %
- (key, value, actual[key]))
+ (safe_repr(key), safe_repr(value),
+ safe_repr(actual[key])))
if not (missing or mismatched):
return
standardMsg = ''
if missing:
- standardMsg = 'Missing: %r' % ','.join(missing)
+ standardMsg = 'Missing: %s' % ','.join(safe_repr(m) for m in
+ missing)
if mismatched:
if standardMsg:
standardMsg += '; '
@@ -758,10 +812,8 @@ class TestCase(object):
try:
expected = set(expected_seq)
actual = set(actual_seq)
- missing = list(expected.difference(actual))
- unexpected = list(actual.difference(expected))
- missing.sort()
- unexpected.sort()
+ missing = sorted(expected.difference(actual))
+ unexpected = sorted(actual.difference(expected))
except TypeError:
# Fall back to slower list-compare if any of the objects are
# not hashable.
@@ -771,16 +823,17 @@ class TestCase(object):
expected.sort()
actual.sort()
except TypeError:
- missing, unexpected = util.unorderable_list_difference(expected,
- actual)
- else:
- missing, unexpected = util.sorted_list_difference(expected,
+ missing, unexpected = unorderable_list_difference(expected,
actual)
+ else:
+ missing, unexpected = sorted_list_difference(expected, actual)
errors = []
if missing:
- errors.append('Expected, but missing:\n %r' % missing)
+ errors.append('Expected, but missing:\n %s' %
+ safe_repr(missing))
if unexpected:
- errors.append('Unexpected, but present:\n %r' % unexpected)
+ errors.append('Unexpected, but present:\n %s' %
+ safe_repr(unexpected))
if errors:
standardMsg = '\n'.join(errors)
self.fail(self._formatMessage(msg, standardMsg))
@@ -800,31 +853,31 @@ class TestCase(object):
def assertLess(self, a, b, msg=None):
"""Just like self.assertTrue(a < b), but with a nicer default message."""
if not a < b:
- standardMsg = '%r not less than %r' % (a, b)
+ standardMsg = '%s not less than %s' % (safe_repr(a), safe_repr(b))
self.fail(self._formatMessage(msg, standardMsg))
def assertLessEqual(self, a, b, msg=None):
"""Just like self.assertTrue(a <= b), but with a nicer default message."""
if not a <= b:
- standardMsg = '%r not less than or equal to %r' % (a, b)
+ standardMsg = '%s not less than or equal to %s' % (safe_repr(a), safe_repr(b))
self.fail(self._formatMessage(msg, standardMsg))
def assertGreater(self, a, b, msg=None):
"""Just like self.assertTrue(a > b), but with a nicer default message."""
if not a > b:
- standardMsg = '%r not greater than %r' % (a, b)
+ standardMsg = '%s not greater than %s' % (safe_repr(a), safe_repr(b))
self.fail(self._formatMessage(msg, standardMsg))
def assertGreaterEqual(self, a, b, msg=None):
"""Just like self.assertTrue(a >= b), but with a nicer default message."""
if not a >= b:
- standardMsg = '%r not greater than or equal to %r' % (a, b)
+ standardMsg = '%s not greater than or equal to %s' % (safe_repr(a), safe_repr(b))
self.fail(self._formatMessage(msg, standardMsg))
def assertIsNone(self, obj, msg=None):
"""Same as self.assertTrue(obj is None), with a nicer default message."""
if obj is not None:
- standardMsg = '%r is not None' % obj
+ standardMsg = '%s is not None' % (safe_repr(obj),)
self.fail(self._formatMessage(msg, standardMsg))
def assertIsNotNone(self, obj, msg=None):
@@ -837,13 +890,13 @@ class TestCase(object):
"""Same as self.assertTrue(isinstance(obj, cls)), with a nicer
default message."""
if not isinstance(obj, cls):
- standardMsg = '%r is not an instance of %r' % (obj, cls)
+ standardMsg = '%s is not an instance of %r' % (safe_repr(obj), cls)
self.fail(self._formatMessage(msg, standardMsg))
def assertNotIsInstance(self, obj, cls, msg=None):
"""Included for symmetry with assertIsInstance."""
if isinstance(obj, cls):
- standardMsg = '%r is an instance of %r' % (obj, cls)
+ standardMsg = '%s is an instance of %r' % (safe_repr(obj), cls)
self.fail(self._formatMessage(msg, standardMsg))
def assertRaisesRegexp(self, expected_exception, expected_regexp,
@@ -921,11 +974,11 @@ class FunctionTestCase(TestCase):
self._testFunc, self._description))
def __str__(self):
- return "%s (%s)" % (util.strclass(self.__class__),
+ return "%s (%s)" % (strclass(self.__class__),
self._testFunc.__name__)
def __repr__(self):
- return "<%s testFunc=%s>" % (util.strclass(self.__class__),
+ return "<%s tec=%s>" % (strclass(self.__class__),
self._testFunc)
def shortDescription(self):
diff --git a/Lib/unittest/result.py b/Lib/unittest/result.py
index 4538304908..746967ec28 100644
--- a/Lib/unittest/result.py
+++ b/Lib/unittest/result.py
@@ -16,7 +16,9 @@ class TestResult(object):
contain tuples of (testcase, exceptioninfo), where exceptioninfo is the
formatted traceback of the error that occurred.
"""
- def __init__(self):
+ _previousTestClass = None
+ _moduleSetUpFailed = False
+ def __init__(self, stream=None, descriptions=None, verbosity=None):
self.failures = []
self.errors = []
self.testsRun = 0
@@ -25,6 +27,9 @@ class TestResult(object):
self.unexpectedSuccesses = []
self.shouldStop = False
+ def printErrors(self):
+ "Called by TestRunner after test run"
+
def startTest(self, test):
"Called when the given test is about to be run"
self.testsRun += 1
@@ -107,6 +112,6 @@ class TestResult(object):
return length
def __repr__(self):
- return "<%s run=%i errors=%i failures=%i>" % \
+ return ("<%s run=%i errors=%i failures=%i>" %
(util.strclass(self.__class__), self.testsRun, len(self.errors),
- len(self.failures))
+ len(self.failures)))
diff --git a/Lib/unittest/runner.py b/Lib/unittest/runner.py
index 3f45f73638..8773d0c5fe 100644
--- a/Lib/unittest/runner.py
+++ b/Lib/unittest/runner.py
@@ -148,15 +148,22 @@ class TextTestRunner(object):
stopTime = time.time()
timeTaken = stopTime - startTime
result.printErrors()
- self.stream.writeln(result.separator2)
+ if hasattr(result, 'separator2'):
+ self.stream.writeln(result.separator2)
run = result.testsRun
self.stream.writeln("Ran %d test%s in %.3fs" %
(run, run != 1 and "s" or "", timeTaken))
self.stream.writeln()
- results = map(len, (result.expectedFailures,
- result.unexpectedSuccesses,
- result.skipped))
- expectedFails, unexpectedSuccesses, skipped = results
+
+ expectedFails = unexpectedSuccesses = skipped = 0
+ try:
+ results = map(len, (result.expectedFailures,
+ result.unexpectedSuccesses,
+ result.skipped))
+ expectedFails, unexpectedSuccesses, skipped = results
+ except AttributeError:
+ pass
+
infos = []
if not result.wasSuccessful():
self.stream.write("FAILED")
diff --git a/Lib/unittest/suite.py b/Lib/unittest/suite.py
index 35b7d91da3..ddd83ae4c6 100644
--- a/Lib/unittest/suite.py
+++ b/Lib/unittest/suite.py
@@ -1,17 +1,13 @@
"""TestSuite"""
+import sys
+
from . import case
from . import util
-class TestSuite(object):
- """A test suite is a composite test consisting of a number of TestCases.
-
- For use, create an instance of TestSuite, then add test case instances.
- When all tests have been added, the suite can be passed to a test
- runner, such as TextTestRunner. It will run the individual test cases
- in the order in which they were added, aggregating the results. When
- subclassing, do not forget to call the base class constructor.
+class BaseTestSuite(object):
+ """A simple test suite that doesn't provide class or module shared fixtures.
"""
def __init__(self, tests=()):
self._tests = []
@@ -67,3 +63,190 @@ class TestSuite(object):
"""Run the tests without collecting errors in a TestResult"""
for test in self:
test.debug()
+
+
+class TestSuite(BaseTestSuite):
+ """A test suite is a composite test consisting of a number of TestCases.
+
+ For use, create an instance of TestSuite, then add test case instances.
+ When all tests have been added, the suite can be passed to a test
+ runner, such as TextTestRunner. It will run the individual test cases
+ in the order in which they were added, aggregating the results. When
+ subclassing, do not forget to call the base class constructor.
+ """
+
+
+ def run(self, result):
+ self._wrapped_run(result)
+ self._tearDownPreviousClass(None, result)
+ self._handleModuleTearDown(result)
+ return result
+
+ ################################
+ # private methods
+ def _wrapped_run(self, result):
+ for test in self:
+ if result.shouldStop:
+ break
+
+ if _isnotsuite(test):
+ self._tearDownPreviousClass(test, result)
+ self._handleModuleFixture(test, result)
+ self._handleClassSetUp(test, result)
+ result._previousTestClass = test.__class__
+
+ if (getattr(test.__class__, '_classSetupFailed', False) or
+ getattr(result, '_moduleSetUpFailed', False)):
+ continue
+
+ if hasattr(test, '_wrapped_run'):
+ test._wrapped_run(result)
+ else:
+ test(result)
+
+ def _handleClassSetUp(self, test, result):
+ previousClass = getattr(result, '_previousTestClass', None)
+ currentClass = test.__class__
+ if currentClass == previousClass:
+ return
+ if result._moduleSetUpFailed:
+ return
+ if getattr(currentClass, "__unittest_skip__", False):
+ return
+
+ currentClass._classSetupFailed = False
+
+ setUpClass = getattr(currentClass, 'setUpClass', None)
+ if setUpClass is not None:
+ try:
+ setUpClass()
+ except:
+ currentClass._classSetupFailed = True
+ self._addClassSetUpError(result, currentClass)
+
+ def _get_previous_module(self, result):
+ previousModule = None
+ previousClass = getattr(result, '_previousTestClass', None)
+ if previousClass is not None:
+ previousModule = previousClass.__module__
+ return previousModule
+
+
+ def _handleModuleFixture(self, test, result):
+ previousModule = self._get_previous_module(result)
+ currentModule = test.__class__.__module__
+ if currentModule == previousModule:
+ return
+
+ self._handleModuleTearDown(result)
+
+
+ result._moduleSetUpFailed = False
+ try:
+ module = sys.modules[currentModule]
+ except KeyError:
+ return
+ setUpModule = getattr(module, 'setUpModule', None)
+ if setUpModule is not None:
+ try:
+ setUpModule()
+ except:
+ result._moduleSetUpFailed = True
+ error = _ErrorHolder('setUpModule (%s)' % currentModule)
+ result.addError(error, sys.exc_info())
+
+ def _handleModuleTearDown(self, result):
+ previousModule = self._get_previous_module(result)
+ if previousModule is None:
+ return
+ if result._moduleSetUpFailed:
+ return
+
+ try:
+ module = sys.modules[previousModule]
+ except KeyError:
+ return
+
+ tearDownModule = getattr(module, 'tearDownModule', None)
+ if tearDownModule is not None:
+ try:
+ tearDownModule()
+ except:
+ error = _ErrorHolder('tearDownModule (%s)' % previousModule)
+ result.addError(error, sys.exc_info())
+
+ def _tearDownPreviousClass(self, test, result):
+ previousClass = getattr(result, '_previousTestClass', None)
+ currentClass = test.__class__
+ if currentClass == previousClass:
+ return
+ if getattr(previousClass, '_classSetupFailed', False):
+ return
+ if getattr(result, '_moduleSetUpFailed', False):
+ return
+ if getattr(previousClass, "__unittest_skip__", False):
+ return
+
+ tearDownClass = getattr(previousClass, 'tearDownClass', None)
+ if tearDownClass is not None:
+ try:
+ tearDownClass()
+ except:
+ self._addClassTearDownError(result)
+
+ def _addClassTearDownError(self, result):
+ className = util.strclass(result._previousTestClass)
+ error = _ErrorHolder('classTearDown (%s)' % className)
+ result.addError(error, sys.exc_info())
+
+ def _addClassSetUpError(self, result, klass):
+ className = util.strclass(klass)
+ error = _ErrorHolder('classSetUp (%s)' % className)
+ result.addError(error, sys.exc_info())
+
+
+class _ErrorHolder(object):
+ """
+ Placeholder for a TestCase inside a result. As far as a TestResult
+ is concerned, this looks exactly like a unit test. Used to insert
+ arbitrary errors into a test suite run.
+ """
+ # Inspired by the ErrorHolder from Twisted:
+ # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
+
+ # attribute used by TestResult._exc_info_to_string
+ failureException = None
+
+ def __init__(self, description):
+ self.description = description
+
+ def id(self):
+ return self.description
+
+ def shortDescription(self):
+ return None
+
+ def __repr__(self):
+ return "<ErrorHolder description=%r>" % (self.description,)
+
+ def __str__(self):
+ return self.id()
+
+ def run(self, result):
+ # could call result.addError(...) - but this test-like object
+ # shouldn't be run anyway
+ pass
+
+ def __call__(self, result):
+ return self.run(result)
+
+ def countTestCases(self):
+ return 0
+
+def _isnotsuite(test):
+ "A crude way to tell apart testcases and suites with duck-typing"
+ try:
+ iter(test)
+ except TypeError:
+ return True
+ return False
diff --git a/Lib/unittest/util.py b/Lib/unittest/util.py
index 26805def31..1c1b600f7d 100644
--- a/Lib/unittest/util.py
+++ b/Lib/unittest/util.py
@@ -1,5 +1,11 @@
"""Various utility functions."""
+def safe_repr(obj):
+ try:
+ return repr(obj)
+ except Exception:
+ return object.__repr__(obj)
+
def strclass(cls):
return "%s.%s" % (cls.__module__, cls.__name__)