Earlier I promised to share the
UnitTestUtilities
and DocMeta
modules. Today, it'll be UnitTestUtilities
.The rationale for this chunk of code was, quite simply, that I found I needed a way to make sure that all of the items that needed to be tested for a given class, abstract class, or interface would be automatically identified in as simple a way as possible.
The test-cases template in the module-template I shared yesterday provides just such hooks (for properties and methods, at least) in the
testPropertyCountAndTests
and testMethodCountAndTests
test-methods, you may recall. Those, in turn depend on two functions provided by UnitTestUtilities
.
Here's what that code looks like at present.
# UnitTestUtilities.py """Provides some utility functions to assist in assuring code-coverage for unit-tests. These functions are predicated on a test structure that looks something like so: ######################################## class ClassNameDerived( ClassName ): def __init__( self ): pass class testClassName( unittest.TestCase ): \"\"\"Unit-tests the ClassName class.\"\"\" def setUp( self ): pass def tearDown( self ): pass # def testDerived( self ): # \"\"\"Testing abstract nature of the ClassName class.\"\"\" # pass # def testFinal( self ): # \"\"\"Testing final nature of the ClassName class.\"\"\" # pass def testConstruction( self ): \"\"\"Testing construction of the ClassName class.\"\"\" pass def testPropertyCountAndTests( self ): \"\"\"Testing the properties of the ClassName class.\"\"\" items = getMemberNames( ClassName )[0] actual = len( items ) expected = 0 self.assertEquals( expected, actual, 'ClassName is expected to have %d properties to test, but %d were dicovered by inspection.' % ( expected, actual ) ) for item in items: self.assertTrue( HasTestFor( self, item ), 'There should be a test for the %s property (test%s), but none was identifiable.' % ( item, item ) ) def testMethodCountAndTests( self ): \"\"\"Testing the methods of the ClassName class.\"\"\" items = getMemberNames( ClassName )[1] actual = len( items ) expected = 0 self.assertEquals( expected, actual, 'ClassName is expected to have %d methods to test, but %d were dicovered by inspection.' % ( expected, actual ) ) for item in items: self.assertTrue( HasTestFor( self, item ), 'There should be a test for the %s method (test%s), but none was identifiable.' % ( item, item ) ) ######################################## The functions, as used in testPropertyCountAndTests and testMethodCountAndTests, assure that there is a test[MethodName] and test[PropertyName] for each *public* method and property, respectively, and raise assertion errors if they don't exist. It is up to the developer to generate those test-methods and to assure that they provide complete coverage (at least so far). """ __author__ = 'Brian D. Allbee' __licence__ = 'http://creativecommons.org/licenses/by-sa/3.0/' __version__ = '0.2' __copyright__ = "Copyright 2011, Brian D. Allbee" __credits__ = ["Brian D. Allbee"] __maintainer__ = "Brian D. Allbee" __email__ = "brian.allbee@gmail.com" __status__ = "Development" ##################################### # Build an "__all__" list to # # support # # "from Namespace.blah import *" # # syntax # ##################################### __all__ = [] ##################################### # Required imports # ##################################### import inspect, types from DocMeta import * ##################################### # Functions defined in the module # ##################################### @ToDo( 'Unit-test the function' ) @ToDo( 'Document exceptions raised' ) @ToDo( 'Value-check arguments' ) @ToDo( 'Type-check arguments' ) @DocumentArgument( 'argument', 'theClass', None, '(Class, required) The class to get property and method names from.' ) @DocumentReturn( types.TupleType ) def getMemberNames( theClass ): """Returns a list of property- and method-names for the specified class.""" classMembers = inspect.getmembers( theClass ) properties = [] methods = [] for member in classMembers: if inspect.isdatadescriptor( member[1] ) and member[0][0] != '_': properties.append( member[0] ) if inspect.ismethod( member[1] ) and member[0][0] != '_': methods.append( member[0] ) return properties, methods __all__ += [ 'getMemberNames' ] @ToDo( 'Unit-test the function' ) @ToDo( 'Document exceptions raised' ) @ToDo( 'Value-check arguments' ) @ToDo( 'Type-check arguments' ) @DocumentArgument( 'argument', 'testCase', None, '(unittest.TestCase, required) The TestCase instance to check for the named test-method in.' ) @DocumentArgument( 'argument', 'itemName', None, '(Class, required) The name of the property or method to look for a test-method for ("test[itemName]").' ) @DocumentReturn( types.BooleanType ) def HasTestFor( testCase, itemName ): """Returns true if the testCase provided has a test-method named 'test[itemName]'.""" classMembers = inspect.getmembers( testCase ) nameToFind = 'test%s' % ( itemName ) for member in classMembers: if member[0] == nameToFind: return True return False __all__ += [ 'HasTestFor' ] ##################################### # Interfaces defined in the module # ##################################### ##################################### # Abstract Classes defined in the # # module # ##################################### ##################################### # Classes defined in the module # ##################################### ##################################### # Package sub-modules and -packages # ##################################### ##################################### # Unit-test the module on main # ##################################### if __name__ == '__main__': import inspect, os, sys, unittest testSuite = unittest.TestSuite() testResults = unittest.TestResult() ################################# # Unit-test Constants # ################################# class testConstants( unittest.TestCase ): """Unit-tests the constants defined in the module.""" # No constants to test pass testSuite.addTests( unittest.TestLoader().loadTestsFromTestCase( testConstants ) ) ################################# # Unit-test Functions # ################################# class Parent( object ): ParentPublic1 = property() ParentPublic2 = property() _ParentProtected1 = property() _ParentProtected2 = property() __ParentPrivate1 = property() __ParentPrivate2 = property() ParentOverrideMe1 = property() def __init__( self ): pass def ParentMethod1( self ): pass def ParentMethod2( self ): pass def _ParentMethod1( self ): pass def _ParentMethod2( self ): pass def __ParentMethod1( self ): pass def __ParentMethod2( self ): pass def ParentOverrideMe1( self ): pass class Child( Parent, object ): ChildPublic1 = property() ChildPublic2 = property() _ChildProtected1 = property() _ChildProtected2 = property() __ChildPrivate1 = property() __ChildPrivate2 = property() ChildOverrideMe1 = property() def __init__( self ): pass def ChildMethod1( self ): pass def ChildMethod2( self ): pass def _ChildMethod1( self ): pass def _ChildMethod2( self ): pass def __ChildMethod1( self ): pass def __ChildMethod2( self ): pass def ParentOverrideMe1( self ): pass class BadParentTestCase( object ): pass class GoodParentTestCase( object ): def testParentPublic1( self ): pass def testParentPublic2( self ): pass def testParentMethod1( self ): pass def testParentMethod2( self ): pass def testParentOverrideMe2( self ): pass class BadChildTestCase( object ): pass class GoodChildTestCase( object ): def testParentPublic1( self ): pass def testParentPublic2( self ): pass def testParentMethod1( self ): pass def testParentMethod2( self ): pass def testChildPublic1( self ): pass def testChildPublic2( self ): pass def testChildMethod1( self ): pass def testChildMethod2( self ): pass def testParentOverrideMe2( self ): pass class testFunctions( unittest.TestCase ): """Unit-tests the module's functions.""" def testgetMemberNames( self ): """Tests the getMemberNames function.""" self.assertEquals( getMemberNames( Parent ), ( ['ParentPublic1', 'ParentPublic2'], ['ParentMethod1', 'ParentMethod2', 'ParentOverrideMe1'] ) ) self.assertEquals( getMemberNames( Child ), ( ['ChildOverrideMe1', 'ChildPublic1', 'ChildPublic2', 'ParentPublic1', 'ParentPublic2'], ['ChildMethod1', 'ChildMethod2', 'ParentMethod1', 'ParentMethod2', 'ParentOverrideMe1'] ) ) def testHasTestFor( self ): """Tests the HasTestFor function.""" goodCases = { GoodParentTestCase:[ 'ParentPublic1', 'ParentPublic2', 'ParentMethod1', 'ParentMethod2', 'ParentOverrideMe2', ], GoodChildTestCase:[ 'ParentPublic1', 'ParentPublic2', 'ParentMethod1', 'ParentMethod2', 'ParentOverrideMe2', 'ChildPublic1', 'ChildPublic2', 'ChildMethod1', 'ChildMethod2', ], } for testCase in goodCases: caseItems = goodCases[ testCase ] for item in caseItems: self.assertTrue( HasTestFor( testCase, item ), 'There should be a test-method in %s for %s (test%s)' % ( testCase.__name__, item, item ) ) badCases = { BadParentTestCase:[ 'ParentPublic1', 'ParentPublic2', 'ParentMethod1', 'ParentMethod2', 'testParentOverrideMe2', ], BadChildTestCase:[ 'ParentPublic1', 'ParentPublic2', 'ParentMethod1', 'ParentMethod2', 'ParentOverrideMe2', 'ChildPublic1', 'ChildPublic2', 'ChildMethod1', 'ChildMethod2', ], } for testCase in badCases: caseItems = badCases[ testCase ] for item in caseItems: self.assertFalse( HasTestFor( testCase, item ), 'There should not be a test-method in %s for %s (test%s)' % ( testCase.__name__, item, item ) ) testSuite.addTests( unittest.TestLoader().loadTestsFromTestCase( testFunctions ) ) ################################# # Unit-test Interfaces # ################################# ################################# # Unit-test Abstract Classes # ################################# ################################# # Unit-test Classes # ################################# # Run unit-tests and install print '#'*79 print 'Running unit-tests' testSuite.run( testResults ) print '#'*79 if testResults.errors == [] and testResults.failures == []: print 'All unit-tests passed! Ready for build/copy!' import shutil sitePackagesDirs = [] for path in sys.path: if path.split( os.sep )[-1].lower() == 'site-packages': sitePackagesDirs.append( path ) for path in sitePackagesDirs: destinationFile = path + os.sep + __file__ try: shutil.copyfile( './%s' % __file__, destinationFile ) print 'Copied %s to %s' % ( __file__, path ) except: print 'Could not copy file %s to location %s' % ( __file__, path ) else: print 'Unit-tests failed (see items below)' print '#'*79 if testResults.errors: print 'Errors' print '#' + '-'*77 + '#' for error in testResults.errors: print error[1] print '#' + '-'*77 + '#' print if testResults.failures: if testResults.errors: print '#'*79 print 'Failures' print '#' + '-'*77 + '#' for failure in testResults.failures: print failure[1] print '#' + '-'*77 + '#' print print '#'*79
You might note that there's several
@ToDo
annotations here — I'm planning on getting to each of those in the near future, but didn't want to hold up sharing this code for want of those items, particularly as the main unit-tests are functional and pass...
No comments:
Post a Comment