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
TypedList
with values presented to the constructor; - Appending items to a
TypedList
with+
; - Appending items to a
TypedList
with+=
; - Appending items to a
TypedList
withappend
; - Appending (or extending) items to a
TypedList
withextend
; - Setting the value in a specific
TypedList
location withTypedList[n] = value
; - Setting one or more values in a slice of a
TypedList
withTypedList[i:j] = value
; - Inserting a value into a specific
TypedList
location 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
TypedList
instance 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
TypedList
instance 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
TypedList
instance as we do on an equivalentlist
instance, 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
TypedList
needs to be able to do that as well. - 55-58
- Tests the "+" operator as it applies to
TypedList
s - 62-65
- Tests the "+=" operator as it applies to
TypedList
s - 69-72
- Tests the
append
method - 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
extend
method - 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
insert
method - 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
TypedList
functionality 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