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