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...
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.
No comments:
Post a Comment