The next step, if we approach this in a TDD fashion, is probably to define our
unit-tests. Technically, this step probably should've been undertaken
earlier, perhaps even before starting to track down the methods that we'd need
to override, but since I expected that there were going to be a lot of them (or
at least a lot to evaluate), I wanted to get that started fairly early. The
unit-tests need to encompass all of the expected uses that we can put a
TypedList to, and need to include both passing and failing (negative)
tests in order to assure that they're covering the code sufficiently.
At the minimum, that means that we need to test each of the following cases:
- Instantiation of a
TypedListwith values presented to the constructor; - Appending items to a
TypedListwith+; - Appending items to a
TypedListwith+=; - Appending items to a
TypedListwithappend; - Appending (or extending) items to a
TypedListwithextend; - Setting the value in a specific
TypedListlocation withTypedList[n] = value; - Setting one or more values in a slice of a
TypedListwithTypedList[i:j] = value; - Inserting a value into a specific
TypedListlocation withinsert
Given that we need to test all of these both positively (to prove that basic list
behavior for allowed types is preserved) and negatively (to raise errors when they
are presented with an invalid type), there are a lot of assertions that
have to be made in the testListBehavior test-method. Most of the rest
of the test-methods are pretty typical of the pattern I've followed thus far, so
I won't spend a lot of time commenting on those.
Unit-tests
class TypedListDerived( TypedList ):
def __init__( self, memberTypes, iterable=None ):
TypedList.__init__( self, memberTypes, iterable )
class testTypedList( unittest.TestCase ):
"""Unit-tests the TypedList class."""
def setUp( self ):
pass
def tearDown( self ):
pass
def testFinal( self ):
"""Testing final nature of the TypedList class."""
try:
testObject = TypedListDerived( [ types.IntType ] )
self.fail( 'TypedList is nominally a final class, and is not intended to be extended.' )
except NotImplementedError:
pass
except Exception, error:
self.fail( 'TypedList is expected to raise NotImplementedError if a derived class is instantiated, but %s was raised instead:\n %s' % ( error.__class__.__name__, error ) )
testObject = TypedList( [ types.IntType ] )
self.assertEquals( testObject, [], 'A Typedlist constructed with no values should equal an empty list.' )
def testPropertyCountAndTests( self ):
"""Testing the properties of the TypedList class."""
items = getMemberNames( TypedList )[0]
actual = len( items )
expected = 1
self.assertEquals( expected, actual, 'TypedList 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 TypedList class."""
items = getMemberNames( TypedList )[1]
actual = len( items )
expected = 4
self.assertEquals( expected, actual, 'TypedList 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 ) )
# Test list behavior
def testListBehavior( self ):
"""Unit-tests list-like behavior of TypedList."""
# Test good values - all should pass
goodValues = [ -1, 0, 1, 2, -1L, 0L, 1L, 2L, -1.0, 0.0, 1.0, 2.0 ]
# Test creation with an iterable
testObject = TypedList( [ types.IntType, types.LongType, types.FloatType ], goodValues )
self.assertEquals( testObject, goodValues, 'Values set in a TypedList at creation should act like values set in a normal list at creation.' )
# Test L = L + i structure
testObject = TypedList( [ types.IntType, types.LongType, types.FloatType ] )
compareList = []
for testValue in goodValues:
compareList = compareList + [ testValue ]
testObject = testObject + [ testValue ]
self.assertEquals( testObject, compareList, 'Values added to a TypedList should act like values added to a normal list.' )
# Test L = L += i structure
testObject = TypedList( [ types.IntType, types.LongType, types.FloatType ] )
compareList = []
for testValue in goodValues:
compareList += [ testValue ]
testObject += [ testValue ]
self.assertEquals( testObject, compareList, 'Values added to a TypedList should act like values added to a normal list.' )
# Test L.append equivalent
testObject = TypedList( [ types.IntType, types.LongType, types.FloatType ] )
compareList = []
for testValue in goodValues:
compareList.append( testValue )
testObject.append( testValue )
self.assertEquals( testObject, compareList, 'Values appended to a TypedList should act like values appended to a normal list.' )
# Test L.extend equivalent
testObject = TypedList( [ types.IntType, types.LongType, types.FloatType ] )
compareList = []
for testValue in goodValues:
compareList.extend( [ testValue ] )
testObject.extend( [ testValue ] )
self.assertEquals( testObject, compareList, 'Values extended to a TypedList should act like values extended to a normal list.' )
# Test L.insert equivalent
testObject = TypedList( [ types.IntType, types.LongType, types.FloatType ] )
compareList = []
for testValue in goodValues:
compareList.insert( 0, testValue )
testObject.insert( 0, testValue )
self.assertEquals( testObject, compareList, 'Values inserted to a TypedList should act like values inserted to a normal list.' )
# Test L[index] = value equivalent
testObject = TypedList( [ types.IntType, types.LongType, types.FloatType ], [ 0 ] )
compareList = [ 0 ]
for testValue in goodValues:
compareList[0] = testValue
testObject[0] = testValue
self.assertEquals( testObject, compareList, 'Values set in a position in a TypedList should act like values set in a position in a normal list.' )
# Test L[slice] = [ i, j ] equivalent
testObject = TypedList( [ types.IntType, types.LongType, types.FloatType ], [ 0, 0, 0 ] )
compareList = [ 0, 0, 0 ]
goodValues = [ [ -1, 0 ], [ 1, 2 ] ]
for testValue in goodValues:
compareList[0:1] = testValue
testObject[0:1] = testValue
self.assertEquals( testObject, compareList, 'Values set in a slice position in a TypedList should act like values set in a slice position in a normal list.' )
# Test bad values - all should raise errors
badValues = [ None, 'string', [ 'ook' ] ]
testObject = TypedList( [ types.IntType, types.LongType, types.FloatType ] )
# Test L = L + i structure
for testValue in badValues:
try:
testObject = testObject + testValue
self.fail( 'Adding %s with "+" to a TypedList that doesn\'t allow that type should raise a TypeError' % ( testValue ) )
except TypeError:
pass
except Exception, error:
self.fail( 'Adding %s with "+" to a TypedList that doesn\'t allow that type should raise a TypeError, but %s was raised instead:\n %s' % ( testValue, error.__class__.__name__, error ) )
# Test L = L += i structure
for testValue in badValues:
try:
testObject += testValue
self.fail( 'Adding %s with "+=" to a TypedList that doesn\'t allow that type should raise a TypeError' % ( testValue ) )
except TypeError:
pass
except Exception, error:
self.fail( 'Adding %s with "+=" to a TypedList that doesn\'t allow that type should raise a TypeError, but %s was raised instead:\n %s' % ( testValue, error.__class__.__name__, error ) )
# Test L.append equivalent
for testValue in badValues:
try:
testObject.append( testValue )
self.fail( 'Appending %s to a TypedList that doesn\'t allow that type should raise a TypeError' % ( testValue ) )
except TypeError:
pass
except Exception, error:
self.fail( 'Appending %s to a TypedList that doesn\'t allow that type should raise a TypeError, but %s was raised instead:\n %s' % ( testValue, error.__class__.__name__, error ) )
# Test L.extend equivalent
for testValue in badValues:
try:
testObject.extend( [ testValue ] )
self.fail( 'Extending %s to a TypedList that doesn\'t allow that type should raise a TypeError' % ( testValue ) )
except TypeError:
pass
except Exception, error:
self.fail( 'Extending %s to a TypedList that doesn\'t allow that type should raise a TypeError, but %s was raised instead:\n %s' % ( testValue, error.__class__.__name__, error ) )
# Test L.insert equivalent
for testValue in badValues:
try:
testObject.insert( 0, testValue )
self.fail( 'Inserting %s to a TypedList that doesn\'t allow that type should raise a TypeError' % ( testValue ) )
except TypeError:
pass
except Exception, error:
self.fail( 'Inserting %s to a TypedList that doesn\'t allow that type should raise a TypeError, but %s was raised instead:\n %s' % ( testValue, error.__class__.__name__, error ) )
# Test L[index] = value equivalent
for testValue in badValues:
try:
testObject[ 0 ] = testValue
self.fail( 'Setting %s to a position in a TypedList that doesn\'t allow that type should raise a TypeError' % ( testValue ) )
except TypeError:
pass
except Exception, error:
self.fail( 'Setting %s to a position in a TypedList that doesn\'t allow that type should raise a TypeError, but %s was raised instead:\n %s' % ( testValue, error.__class__.__name__, error ) )
# Test L[slice] = [ i, j ] equivalent
badValues = [ [ None, 'string' ], [ 'string', None ] ]
testObject = TypedList( [ types.IntType, types.LongType, types.FloatType ], [ 0, 0, 0 ] )
for testValue in badValues:
try:
testObject[ 0:1 ] = testValue
self.fail( 'Setting %s to a slice-position in a TypedList that doesn\'t allow that type should raise a TypeError' % ( testValue ) )
except TypeError:
pass
except Exception, error:
self.fail( 'Setting %s to a slice-position in a TypedList that doesn\'t allow that type should raise a TypeError, but %s was raised instead:\n %s' % ( testValue, error.__class__.__name__, error ) )
# Test Properties
def testMemberTypes( self ):
"""Unit-tests the MemberTypes property of the TypedList class."""
self.assertEquals( TypedList.MemberTypes, BaseTypedCollection.MemberTypes, 'TypedList should inherit the MemberTypes property of BaseTypedCollection.' )
# Test Methods
def testappend( self ):
"""Unit-tests the append method of the TypedList class."""
pass # This is tested in testListBehavior, above.
def testextend( self ):
"""Unit-tests the extend method of the TypedList class."""
pass
def testinsert( self ):
"""Unit-tests the insert method of the TypedList class."""
pass # This is tested in testListBehavior, above.
def testIsValidMemberType( self ):
"""Unit-tests the IsValidMemberType method of the TypedList class."""
self.assertEquals( TypedList.IsValidMemberType, BaseTypedCollection.IsValidMemberType, 'TypedList should inherit the IsValidMemberType method of BaseTypedCollection.' )
testSuite.addTests( unittest.TestLoader().loadTestsFromTestCase( testTypedList ) )
- Line(s)
- 48
- Most of these tests will need a simple baseline list to work with, or to
draw test-values from. The
TypedListinstance I'm going to work with will allow integers, floats amd long integers, so all of the "good" values must fall into one of those types. I also want to make sure that it handles positive, zero and negative values correctly, and it never hurts to test for even/odd differences either, which is why there's a value of 2 of each of those types as well. - 50, 53, 60, 67, etc.
- In order to make sure that each test has a fresh, clean value, I'm creating
a new
TypedListinstance each time. - 54, 61, 68, etc.
- All of the "good" value tests are based around the concept that if we
perform the same actions/operations (with the same value[s]) on a
TypedListinstance as we do on an equivalentlistinstance, the end results will be identical. If that proves not to be the case, then the code has broken core list-functionality. Again, in order to assure that there's a fresh comparison-list copy each time, a new list is created. - 50, 51
- Standard lists can be initialized with an iterable set of values, so a
TypedListneeds to be able to do that as well. - 55-58
- Tests the "+" operator as it applies to
TypedLists - 62-65
- Tests the "+=" operator as it applies to
TypedLists - 69-72
- Tests the
appendmethod - this could be broken out into the dedicated test-method created specifically for it (testappend), but it feels better to keep this test-code here along with the other list-behavior tests. - 76-79
- Tests the
extendmethod - this could be broken out into the dedicated test-method created specifically for it (testextend), but it feels better to keep this test-code here along with the other list-behavior tests. - 83-86
- Tests the
insertmethod - this could be broken out into the dedicated test-method created specifically for it (testinsert), but it feels better to keep this test-code here along with the other list-behavior tests. - 90-93
- Tests the ability to set a value at a specific index-location in the
TypedList. - 97
- For the slice-value test, we need a slightly different good-values structure, so we reset it here before proceeding.
- 98-101
- Tests the ability to set values to a slice-location in the
TypedList. - 102-169
- These are the negative tests for the same
TypedListfunctionality and capabilities that have been positively tested in the previous code. They are somewhat simpler, in that there is no comparison that needs to be made, though it might not be a bad idea to check at each iteration that the values in the underlying list haven't changed, but it doesn't feel necessary at this point - if they do, that feels to me like there's something greviously wrong at a level in the language itself that I won't be able to fix anyway.
That's a fair amount of code for today, even if it is all unit-tests, so I'll
stop here, and finish up with the "real" development in my next post. Bear in
mind that we still don't have the actual TypedList
functionality built at all yet - and that the unit-tests will fail, because that
functionality doesn't exist. At this point in time, that's OK - when
all of the "real" code is writtem and all of our tests pass, we're done!
That ought to go pretty quickly, though.
No comments:
Post a Comment