Showing posts with label TypedList. Show all posts
Showing posts with label TypedList. Show all posts

Wednesday, December 7, 2011

Test-driven Development: Implementing TypedList (part 3)

I was also going to undertake some research into the methods that TypedList needed to override or otherwise implement in order to make it work the way it needs to. That research leads me to believe that there are a total of 8 methods that I need to be concerned with:

  • __init__: the object constructor has to check any items supplied to it for type-restrictions, but if there are no invalid types provided, it can call list.__init__ and return whatever comes back from that;
  • __add__: called when concatenating two lists (in this case). I have to assume that any items provided are suspect until proven otherwise. I also couldn't determine how many times this method gets called in cases of multiple additions;
  • __iadd__: called when concatenating with +=, same assumptions as for __add__;
  • __setitem__: called when assigning a value to a specific position within the list. The value is treated as a non-iterable (e.g., passing a list results in a list in the position of the original list);
  • __setslice__: Called when assigning values to a slice, items being set need to be type-checked;
  • append: Called when appending values to the list, items added need to be type-checked;
  • insert: Called when inserting a value to a specific position in the list, the value needs to be type-checked;
  • extend: Called to extend items to the end of the list, same drill;
This list of methods was mostly generated by looking at the documentation-sources noted in the last post, but since I wasn't sure exactly what some of the operational parameters for them were (whether they could handle single items only, iterable items, etc., and how they would deal with unexpected types), I also hacked up a quick little test-script to provide some visibility into what exactly happened when various operations and list-methods were called:

class MyList( list, object ):

    def __init__( self, iterable=None ):
        print "MyList.__init__( %s )" % ( iterable )
        list.__init__( self, iterable )
    def __add__( self, items ):
        print "MyList.__add__( %s )" % ( items )
        return list.__add__( self, items )
    def __iadd__( self, items ):
        print "MyList.__iadd__( %s )" % ( items )
        return list.__iadd__( self, items )
    def __setitem__( self, index, item ):
        print "MyList.__setitem__( %s, %s )" % ( index, item )
        return list.__setitem__( self, index, item )
    def __setslice__( self, start, end, items ):
        print "MyList.__setslice__( %s, %s, %s )" % ( start, end, items )
        return list.__setslice__( self, start, end, items )
    def append( self, item ):
        print "MyList.append( %s )" % ( item )
        return list.append( self, item )
    def insert( self, index, item ):
        print "MyList.insert( %s, %s )" % ( index, item )
        return list.insert( self, index, item )
    def extend( self, items ):
        print "MyList.extend( %s )" % ( items )
        return list.extend( self, items )

tester = MyList( ['eek'] )                # uses __init__
tester += [ 'ook' ]                       # uses __iadd__
tester.append( 'eek' )                    # uses append
tester.insert( 1, 'OOOOK!' )              # uses insert
tester[1:2] = [ 'spam', 'eggs' ]          # uses __setslice__
tester += [ 'oooo', 'ooooo!' ]            # uses __iadd__
tester[2] = 'bork!'                       # uses __setitem__
tester.extend( [ 'eeee', 'eeee' ] )       # uses extend
tester = tester + [ 'mooo!' ] + [ 'me' ]  # uses __add__
print
print tester

This little script provided a lot of insight, actually - Though I'd seen some of the documentation for __add__ and __iadd__, for example, it never really stuck in my head that these were the methods that were called when + and += operators were used.

So, with our unit-tests set, and a solid feel for what the code needed to override from list, TypedList was pretty easily written:

TypedList (Nominal final class):

class TypedList( BaseTypedCollection, list ):
    """Represents a strongly-typed List."""

    ##################################
    # Class Attributes               #
    ##################################

    ##################################
    # Class Property-Getter Methods  #
    ##################################

    ##################################
    # Class Property-Setter Methods  #
    ##################################

    ##################################
    # Class Property-Deleter Methods #
    ##################################

    ##################################
    # Class Properties               #
    ##################################

    ##################################
    # Object Constructor             #
    ##################################

    @DocumentArgument( 'argument', 'self', None, SelfDocumentationString )
    @DocumentArgument( 'argument', 'memberTypes', None, '(Iterable, required) An iterable collection of item-types to be allowed as members in the list.' )
    @DocumentArgument( 'argument', 'iterable', None, '(Iterable, required) An iterable collection of item-values to populate the TypedList with at creation.' )
    def __init__( self, memberTypes, iterable=None ):
        """Object constructor."""
        # Nominally final: Don't allow any class other than this one
        if self.__class__ != TypedList:
            raise NotImplementedError( 'TypedList is (nominally) a final class, and is not intended to be derived from.' )
        self._SetMemberTypes( memberTypes )
        list.__init__( self )
        if iterable != None:
            for item in iterable:
                self.append( item )

    ##################################
    # Object Destructor              #
    ##################################

    ##################################
    # Class Methods                  #
    ##################################

    ##################################
    # Methods overridden from list   #
    ##################################

    @DocumentArgument( 'argument', 'self', None, SelfDocumentationString )
    def __add__( self, items ):
        """Called when adding items to the list with a "+" (e.g., TL + items)."""
        if not isinstance( items, list ):
            raise TypeError( 'TypedList.__add__ expects a list (or list-derived) instance of values.' % ( self._memberTypes ) )
        for item in items:
            if not self.IsValidMemberType( item ):
                raise TypeError( 'TypedList.__add__ expects a list (or list-derived) instance of values of any of %s. %s is not valid.' % ( self._memberTypes, items ) )
        return list.__add__( self, items )

    @DocumentArgument( 'argument', 'self', None, SelfDocumentationString )
    def __iadd__( self, items ):
        """Called when adding items to the list with a "+=" (e.g., TL += item)."""
        if not isinstance( items, list ):
            raise TypeError( 'TypedList.__iadd__ expects a list (or list-derived) instance of values.' % ( self._memberTypes ) )
        for item in items:
            if not self.IsValidMemberType( item ):
                raise TypeError( 'TypedList.__iadd__ expects a list (or list-derived) instance of values of any of %s. %s is not valid.' % ( self._memberTypes, items ) )
        return list.__iadd__( self, items )

    @DocumentArgument( 'argument', 'self', None, SelfDocumentationString )
    def __setitem__( self, index, item ):
        """Called when setting the value of a specified location in the list (TL[n] = item)."""
        if not self.IsValidMemberType( item ):
            raise TypeError( 'TypedList.__setitem__ expects an instance of any of %s.' % ( self._memberTypes ) )
        return list.__setitem__( self, index, item )

    @DocumentArgument( 'argument', 'self', None, SelfDocumentationString )
    def __setslice__( self, start, end, items ):
        """Called when setting the value(s) of a slice-location in the list (TL[1:2] = item)."""
        for item in items:
            if not self.IsValidMemberType( item ):
                raise TypeError( 'TypedList.__setslice__ expects an iterable of instances of any of %s.' % ( self._memberTypes ) )
        return list.__setslice__( self, start, end, items )

    @DocumentArgument( 'argument', 'self', None, SelfDocumentationString )
    def append( self, item ):
        """Called when appending items to the list."""
        if not self.IsValidMemberType( item ):
            raise TypeError( 'TypedList.append expects an instance of any of %s.' % ( self._memberTypes ) )
        return list.append( self, item )

    @DocumentArgument( 'argument', 'self', None, SelfDocumentationString )
    def extend( self, items ):
        """Called when extending items to a the end of the list."""
        if not isinstance( items, list ):
            raise TypeError( 'TypedList.extend expects a list (or list-derived) instance of values.' % ( self._memberTypes ) )
        for item in items:
            if not self.IsValidMemberType( item ):
                raise TypeError( 'TypedList.extend expects a list (or list-derived) instance of values of any of %s. %s is not valid.' % ( self._memberTypes, items ) )
        return list.extend( self, items )

    @DocumentArgument( 'argument', 'self', None, SelfDocumentationString )
    def insert( self, index, item ):
        """Called when inserting items to a specified location in the list (TL[1] = item)."""
        if not self.IsValidMemberType( item ):
            raise TypeError( 'TypedList.insert expects an instance of any of %s.' % ( self._memberTypes ) )
        return list.insert( self, index, item )

__all__ += [ 'TypedList' ]

Ultimately, as with TypedDictionary earlier, there's not a huge amount of code to be written. Python's built-ins and the methods specific to adding items to or replacing items in a list were the only methods that had to be overridden, and there were only two patterns for those modifications:

  • If the method expected a list-type (e.g., __add__, __iadd__, __setslice__ and extend):
    • The supplied items are checked to make sure that they are derived from a list. This allows list-types and extensions of list types (like Typedlist) to be used.
    • Once the basic supplied type is successfully checked, the items in it have to be individually type-checked (using IsValidMemberType), and if any of them are invalid, raise an error.
    • If everything is good, then call (and return) the parent list-class method with the supplied value(s).
  • If the method expects a non-list instance value, then all that need be done is type-check the value (again, with IsValidMemberType):
    • If it's invalid, raise a TypeError.
    • Otherwise, call (and return) the parent list-class method with the supplied value.

Line(s)
34-35
Treat TypedList as a final class, and don't allow extension.
36
Set the member-types allowed by the instance.
37
Makes sure that the instance calls a basic list-constructor. We can (and should) ignore any supplied instance-values in the iterable argument when calling the parent constructor, though, because we want to use the overridden append method to build out the instance's list of values.
38-40
Checks to see if the constructor is being passed an iterable collection of values to populate the instance with, loops over them if they are supplied, and appends them to the instance's values one by one.
I could've just as easily looped over any supplied iterable values in the constructor, raising an error at the first bad type supplied, and then called the parent constructor with the iterable value. I have a gut-level feeling, however, that taking that approach could raise issues in the future, and since I'd've had to iterate over the values anyway, it just seemed safer to implement the way I did. I can't justify it, but I'm going to trust my instincts.
55-62, 65-72, 82-87, 97-104
Uses the list-checking pattern noted above to override __add__, __iadd__, __setslice__ and extend, respectively.
75-81, 90-94, 107-111
Uses the value-checking pattern noted above to override __setitem__, append and insert, respectively.

Monday, December 5, 2011

Test-driven Development: Implementing TypedList (part 2)

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 with append;
  • Appending (or extending) items to a TypedList with extend;
  • Setting the value in a specific TypedList location with TypedList[n] = value;
  • Setting one or more values in a slice of a TypedList with TypedList[i:j] = value;
  • Inserting a value into a specific TypedList location with insert

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 equivalent list 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 TypedLists
62-65
Tests the "+=" operator as it applies to TypedLists
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.

Friday, December 2, 2011

Test-driven Development: Implementing TypedList (part 1)

As I've been contemplating the next steps in the DataConnectors module, I've been waffling back and forth on how best to implement certain aspects of it. That's usually a sign, for me at least, that I need to take a step back and let things percolate for a while. I mentioned that I'd want/need to implement the TypedList before I got to the concrete classes of DataConnectors anyway, so:

In today's post, I'm going to tackle the creation of the TypedList class in the TypedContainers module. The shared code can be found at dl.dropbox.com/u/1917253/site-packages/TypedContainers.py, as noted when I posted the TypedDictionary some time back. I'm going to approach this from a slightly different angle, though - I'm going to start with as little as I can on the class-definition, build out the unit-tests that it needs in order to do what it needs to do, and write code until the tests pass. If this sounds familiar to you, then you're probably already using Test-Driven Development (TDD).

Part of the reason I'm taking this approach is because the standard unit-test structure that I use (specifically the testPropertyCountAndTests and testMethodCountAndTests test-methods that've been part of all of the recent test-code I've shown make the discovery of the underlying methods and their signatures fairly easy, though it can also be a bit on the tedious side when there's a significant number of properties or methods to be discovered. Another option, and one that might be considerably faster, is to just build a stub-class extending the base that we're starting with (a list in this case), then take a look at it's members with dir() or with pydoc. Use whatever works best, or is easiest for you.

So, to start with, we'll define the TypedList class with as little to it as we can manage. That means that it must inherit from the Python list, and, since we're going to be using the MemberTypes functionality provided by BaseTypedCollection, it needs to inherit from that as well. The bare-bones starting-point, then looks something like this:

class TypedList( BaseTypedCollection, list ):
    """Represents a strongly-typed List."""

    ##################################
    # Class Attributes               #
    ##################################

    ##################################
    # Class Property-Getter Methods  #
    ##################################

    ##################################
    # Class Property-Setter Methods  #
    ##################################

    ##################################
    # Class Property-Deleter Methods #
    ##################################

    ##################################
    # Class Properties               #
    ##################################

    ##################################
    # Object Constructor             #
    ##################################

    @DocumentArgument( 'argument', 'self', None, SelfDocumentationString )
    @DocumentArgument( 'argument', 'memberTypes', None, '(Iterable, required) An iterable collection of item-types to be allowed as members in the list.' )
    def __init__( self, memberTypes ):
        """Object constructor."""
        # Nominally final: Don't allow any class other than this one
        if self.__class__ != TypedList:
            raise NotImplementedError( 'TypedList is (nominally) a final class, and is not intended to be derived from.' )
        self._SetMemberTypes( memberTypes )
        raise NotImplementedError( 'TypedList is not implemented yet.' )

    ##################################
    # Object Destructor              #
    ##################################

    ##################################
    # Class Methods                  #
    ##################################

__all__ += [ 'TypedList' ]

Next, and more importantly, I'm going to generate the unit-tests for the Typedlist class. I'm going to start with only my standard unit-test stub-code, in order to show how it can be used to reveal the underlying properties and methods of other objects. It's starting-point, then is very simple:

    class TypedListDerived( TypedList ):
        def __init__( self ):
            pass
    
    class testTypedList( unittest.TestCase ):
        """Unit-tests the TypedList class."""
    
        def setUp( self ):
            pass
    
        def tearDown( self ):
            pass
        
#        def testDerived( self ):
#            """Testing abstract nature of the TypedList class."""
#            pass
    
#        def testFinal( self ):
#            """Testing final nature of the TypedList class."""
#            pass
    
        def testConstruction( self ):
            """Testing construction of the TypedList class."""
            pass
    
        def testPropertyCountAndTests( self ):
            """Testing the properties of the TypedList class."""
            items = getMemberNames( TypedList )[0]
            actual = len( items )
            expected = 0
            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 = 0
            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 ) )

    testSuite.addTests( unittest.TestLoader().loadTestsFromTestCase( testTypedList ) )

With that basic test-stub in place, I'm going to run my module, and see what the unit-tests reveal (I'll trim the detailed code-dumps, though, and just show the results):

Running unit-tests
###############################################################################
Unit-tests failed (see items below)
###############################################################################
Failures
#-----------------------------------------------------------------------------#
AssertionError: TypedList is expected to have 0 methods to test, but 1 were 
dicovered by inspection.
#-----------------------------------------------------------------------------#
AssertionError: TypedList is expected to have 0 properties to test, but 1 were 
dicovered by inspection.
#-----------------------------------------------------------------------------#
###############################################################################
So, immediately, we can see that there's at least one property and one method that need to be added to our test-cases. My next step is to see what those items are, and in order to do that, I'll change the expected values in testPropertyCountAndTests and testMethodCountAndTests to match what the test-output results are expecting, then re-run the tests:
Running unit-tests
###############################################################################
Unit-tests failed (see items below)
###############################################################################
Failures
#-----------------------------------------------------------------------------#
AssertionError: There should be a test for the IsValidMemberType method 
(testIsValidMemberType), but none was identifiable.
#-----------------------------------------------------------------------------#
AssertionError: There should be a test for the MemberTypes property 
(testMemberTypes), but none was identifiable.
#-----------------------------------------------------------------------------#
###############################################################################
So, already, the standard unit-test structure has shown that we need to create testIsValidMemberType to test the IsValidMemberType method that TypedList inherits from BaseTypedCollection, and the MemberTypes property, also from BaseTypedCollection.

Since those are actually implemented in BaseTypedCollection, all we really need to test for is that they are the inherited members, though:

        # 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 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.' )

Once those test-methods are in place, we re-run the tests:

###############################################################################
Running unit-tests
###############################################################################
All unit-tests passed! Ready for build/copy!
 + Copied TypedContainers.py to ~/.local/lib/python2.6/site-packages
###############################################################################
Et viola! We now have the members inherited from BaseTypedCollection confirmed. In retrospect, these same two tests should also be added to TypedDictionary, as well as the testMethodCountAndTests and testPropertyCountAndTests test-methods, which I'm going to do now, before I forget. They didn't cause any test-failures, so that was an easy addition...

That doesn't even begin to cover the members (public or otherwise) of a list, though... If we take a look at a list-instance in a terminal, we see that there are actually a lot of members (I actually used a quick little hacked-up script to generate this list so I could show what types of members they all are):

  • __add__ ............ <method-wrapper '__add__'...
  • __class__ .......... <type 'list'>
  • __contains__ ....... <method-wrapper '__contains__'...
  • __delattr__ ........ <method-wrapper '__delattr__'...
  • __delitem__ ........ <method-wrapper '__delitem__'...
  • __delslice__ ....... <method-wrapper '__delslice__'...
  • __doc__ ............ list() -> new empty list
    list(iterable) -> new list initialized from iterable's items
  • __eq__ ............. <method-wrapper '__eq__'...
  • __format__ ......... <built-in method __format__...
  • __ge__ ............. <method-wrapper '__ge__'...
  • __getattribute__ ... <method-wrapper '__getattribute__'...
  • __getitem__ ........ <built-in method __getitem__...
  • __getslice__ ....... <method-wrapper '__getslice__'...
  • __gt__ ............. <method-wrapper '__gt__'...
  • __hash__ ........... None
  • __iadd__ ........... <method-wrapper '__iadd__'...
  • __imul__ ........... <method-wrapper '__imul__'...
  • __init__ ........... <method-wrapper '__init__'...
  • __iter__ ........... <method-wrapper '__iter__'...
  • __le__ ............. <method-wrapper '__le__'...
  • __len__ ............ <method-wrapper '__len__'...
  • __lt__ ............. <method-wrapper '__lt__'...
  • __mul__ ............ <method-wrapper '__mul__'...
  • __ne__ ............. <method-wrapper '__ne__'...
  • __new__ ............ <built-in method __new__...
  • __reduce__ ......... <built-in method __reduce__...
  • __reduce_ex__ ...... <built-in method __reduce_ex__...
  • __repr__ ........... <method-wrapper '__repr__'...
  • __reversed__ ....... <built-in method __reversed__...
  • __rmul__ ........... <method-wrapper '__rmul__'...
  • __setattr__ ........ <method-wrapper '__setattr__'...
  • __setitem__ ........ <method-wrapper '__setitem__'...
  • __setslice__ ....... <method-wrapper '__setslice__'...
  • __sizeof__ ......... <built-in method __sizeof__...
  • __str__ ............ <method-wrapper '__str__'...
  • __subclasshook__ ... <built-in method __subclasshook__...
  • append ............. <built-in method append...
  • count .............. <built-in method count...
  • extend ............. <built-in method extend...
  • index .............. <built-in method index...
  • insert ............. <built-in method insert...
  • pop ................ <built-in method pop...
  • remove ............. <built-in method remove...
  • reverse ............ <built-in method reverse...
  • sort ............... <built-in method sort...
The reason that the various public members from append through sort at the end of this list don't raise errors in the methods-count test is because they are built-ins, rather than "normal" methods. It would be possible to add a testBuiltinCountAndTests test-method, using the same pattern as testMethodCountAndTests and testPropertyCountAndTests, but that would require modification of UnitTestUtilities that I don't want to get into right now (I'll note it for later addition, though, don't worry...).

Instead, I'm going to take a look at the pydoc output for my class, and/or dig around a bit in the Python list documentation and Emulating container types documentation for some guidance on what all of these members do. As a fall-back, in case I don't have network-access to check the online documentation, I could also create a simple list-extension class, override all of it's methods to print out that they were called (and with what arguments) before calling the parent list-methods, and see what happens when I do normal list-manipulation. I'll need to look at those normal list-manipulations anyway in order to build out unit-tests for basic list capabilities for the TypedList, so that has benefits beyond just figuring out what each method does. But that'd be time-consuming, with 45(!) items to look into, so I'm going to check the documentation first...

And that seems like a good spot to stop for today.