I hadn't actually shown this aspect of the TestValues
class yet,
but it's an extension of the built-in Python list
type:
@describe.InitClass()
class TestValues( list, object ):
"""
Represents a collection of standard test-values, and provides filtering of
those values."""
#-----------------------------------#
# Class attributes (and instance- #
# attribute default values) #
#-----------------------------------#
As such, it's got a lot of functionality already available that originates with
the underlying list
class that it extends. The decision to extend
TestValues
from list
was one that I went back and forth
on for a while — On one hand, I didn't want to have to implement all of the
functionality that I expected TestValues
to need from scratch if
basing it off of list
. Chances are good that I'd have ended up using
a list
as an internal data-storage for TestValues
values anyway, and just wrapping the TestValues
class around it.
On the other hand, I wasn't really sure what functionality a list
brought to the table that I didn't want. A list
has 30-odd
methods for various purposes, and I ended up having to take a substantial look at
a fair number of them before deciding which ones were still valid and which ones
weren't. Here's what that process looked like...
What does a list
do already?
Before I could really determine what (if any) properties and methods of the
base list
type I wanted to override or otherwise tweak, I needed
to know what it's got, and what all it's members do.
Fortunately, between the documentation about
emulating
container types and some bits and pieces from the __doc__
s of
each member, that turned out to be a pretty easy task. The properties and methods
that I evaluated are:
__add__
- Called to implement the
+
operator, used when concatenating instances.
Will need to be examined in order to assure that concatenation yields an instance ofTestValues
rather than a genericlist
, but that should be the case by default as long as the object being added to is aTestValues
instance. __contains__
- Called to perform membership-test operations. Shouldn't need to be altered.
__delitem__
- Called to delete individual items or slices from the instance. Shouldn't need to be altered.
__delslice__
- Called to delete a slice from the instance. Shouldn't need to be altered.
__eq__
- Called to implement the
==
operator, testing for equality.
Will need to be examined to determine whether comparison will work as-is, and whether it needs to be altered for comparison of instances with equivalent non-instance sequence-objects. __ge__
- Called to implement the
>=
operator, testing for relative size. Will need to be examined to determine whether comparison will work as-is, and whether it needs to be altered for comparison of instances with equivalent non-instance sequence-objects. __getitem__
- Called to get individual items or slices from the instance. Shouldn't need to be altered.
__getslice__
- Called to get a slice of the instance. Shouldn't need to be altered.
__gt__
- Called to implement the
>
operator, testing for relative size. Will need to be examined to determine whether comparison will work as-is, and whether it needs to be altered for comparison of instances with equivalent non-instance sequence-objects. __iadd__
- Called to implement the
+=
operator, used when concatenating instances.
Will need to be examined in order to assure that concatenation yields an instance ofTestValues
rather than a genericlist
, but that should be the case by default as long as the object being added to is aTestValues
instance. __imul__
- Called to implement the
*=
operator. Since this normally yields a number of duplicates of the members of the instance in alist
, and that's not something that seems useful (all test-values should ideally be distinct), I may want to override this to have it throw an error. __iter__
- Called to return an iterator for the instance. Shouldn't need to be altered.
__le__
- Called to implement the
<=
operator, testing for relative size. Will need to be examined to determine whether comparison will work as-is, and whether it needs to be altered for comparison of instances with equivalent non-instance sequence-objects. __len__
- Called to implement the built-in
len
function. Shouldn't need to be altered. __lt__
- Called to implement the
<
operator, testing for relative size. Will need to be examined to determine whether comparison will work as-is, and whether it needs to be altered for comparison of instances with equivalent non-instance sequence-objects. __mul__
- Called to implement the
*
operator. Since this normally yields a number of duplicates of the members of the instance in alist
, and that's not something that seems useful (all test-values should ideally be distinct), I may want to override this to have it throw an error. __ne__
- Called to implement the
!=
and<>
operators, testing for inequality.
Will need to be examined to determine whether comparison will work as-is, and whether it needs to be altered for comparison of instances with equivalent non-instance sequence-objects. __reversed__
- Called to return a reversed iterator for the instance. Shouldn't need to be altered.
__rmul__
- Called to implement the
*
operator. Since this normally yields a number of duplicates of the members of the instance in alist
, and that's not something that seems useful (all test-values should ideally be distinct), I may want to override this to have it throw an error. __setitem__
- Called to set individual items or slices in the instance. Shouldn't need to be altered.
__setslice__
- Called to set a slice in the instance. Shouldn't need to be altered.
append
- Called to append a member to an instance. Shouldn't need to be altered.
count
- Called to count the number of occurrences of a value in the members of an instance. Shouldn't need to be altered.
extend
- Called to extend the instance by appending elements from the supplied iterable. Shouldn't need to be altered.
index
- Called to return the index-position of the first occurrence of the value in the members of the instance. Shouldn't need to be altered.
insert
- Called to insert a value into the members of the instance at a given index-position. Shouldn't need to be altered.
pop
- Called to remove and return a member-item from the instance, at an optional index-position. Shouldn't need to be altered.
remove
- Called to remove the first occurrence of a member value from the members
of the instance.
This will need to be altered, if only to allow an iterable of values to be passed, andTextValues.remove
should remove all occurrences of the specified value(s) from the member collection. reverse
- Called to reverse the sequence of members in the instance. Shouldn't need to be altered.
sort
- Called to sort the members of the instance. Shouldn't need to be altered.
shouldn't need to be alteredcategory, and only one item solidly fell into the
will need to be alteredcategory (
remove
). There were three categories of
list
members that needed alteration or examination at a minimum:
- Comparison-operator-related (various "magic methods")
__eq__
,__ge__
,__gt__
,__le__
,__lt__
and__ne__
- In these cases, all I felt I really needed to do was make sure that they
behaved the same with all of the possible comparisons between
TestValues
andlist
instances. - Mutation-operator-related (also various "magic methods")
__add__
and__iadd__
- These methods, relating to the
+
and+=
operators as applied tolist
s, really just needed verification that when aTestValues
instance was being added to, that the result was still aTestValues
instance. My expectation was that it would be the case, but I had to make sure. Though I don't expect much use of either during unit-testing, I can't rule them out either. __imul__
,__mul__
and__rmul__
- These relate to the
*
and*=
operators as they apply tolist
instances, though I'm not quite certain when__rmul__
gets called as opposed to__mul__
.
Since aTestValues
instance is really intended to provide a reasonably-distinct set of values, and these operations effectively duplicate members in thelist
they are applied to, I had to think through whether I wanted them to override their inherited implementations in order to, perhaps, raise an error of some sort, thus preventing their use. - As a side note: A truly distinct set of values isn't really possible in
a
TestValues
' member-list becausestr
andunicode
values containing the same text will evaluate as equal. - Mutation methods
remove
- As noted above, I want to alter
TestValues.remove
to facilitate the removal of multiple values in a single pass.
Amusingly enough, the first two groups are exactly the kinds of items
that I'd be looking to unit-test if they cropped up in the implementation of other
classes. Because of the way the AddMethodTesting
and AddPropertyTesting
decorators work, though, they would not be automatically detected as testable
members unless they were actively overridden in the derived class. It would be feasible
to simply write overriding methods for each of them that call the parent list
methods, though, and the decorators would pick up on those as local members that
required testing. That feels kind of... wasteful, maybe... so I don't know if I'll
take that approach, but even without doing so, just knowing that they should
be tested is enough to prompt writing those test-methods.
While testing those, at first I believed that I'd need to create an empty
_UnitTestValuePolicy
instance — one whose All
value
had no values — which turned out not to be possible because of the
way the default values were being populated:
def __init__( self, **values ):
"""
Instance initializer"""
# ...
# Set _defaults values from **values members, if they are provided
# - bools
bools = values.get( 'bools' )
if bools:
self._defaults[ 'bools' ] = bools
# - falseish
# ...
In order to allow defaults for the various value-categories to be defined as empty,
I had to alter _UnitTestValuePolicy.__init__
to specifically check for
None
values, as opposed to empty lists (which is what I specified while
trying to create that empty instance:
def __init__( self, **values ):
"""
Instance initializer"""
# - bools
bools = values.get( 'bools' )
if bools == None:
self._defaults[ 'bools' ] = self.__class__._defaults[ 'bools' ]
else:
self._defaults[ 'bools' ] = bools
# - falseish
# ...
That allowed the creation of the empty instance I thought I needed:
emptySource = _UnitTestValuePolicy( bools=[],
falseish=[], floats=[], ints=[], longs=[], none=[],
objects=[], strings=[], trueish=[], unicodes=[] )
print emptySource.All
[]
Checking the Comparison-operator-related Members
For all of the comparison-operation checks, I basically just needed two
list
s and equivalent TestValues
instances:
testValuesList1 = [ 1, 2, 3, 4 ]
testValuesInst1 = TestValues( emptySource, testValuesList1 )
testValuesList2 = [ 4, 3, 2, 1 ]
testValuesInst2 = TestValues( emptySource, testValuesList2 )
Once those were created, the basic checks were fairly simple: Perform the
comparison against all of the relevant list
and TestValues
instance-combinations and make sure that what happened in comparing two lists
also happened when comparing the list
with its equivalent
TestValues
instance. Since each pair of checks should return
the same result, it was an easy matter to just skim downthe list and look for
mismatched result-pairs:
print 'Testing __eq__:'
print 'testValuesList1 == testValuesList1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 == testValuesList1 )
print 'testValuesList1 == testValuesInst1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 == testValuesInst1 )
Testing __eq__: testValuesList1 == testValuesList1 .... True testValuesList1 == testValuesInst1 .... True
print 'Testing __ge__:'
print 'testValuesList1 >= testValuesList1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 >= testValuesList1 )
print 'testValuesList1 >= testValuesInst1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 >= testValuesInst1 )
print 'testValuesList1 >= testValuesList2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 >= testValuesList2 )
print 'testValuesList1 >= testValuesInst2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 >= testValuesInst2 )
print 'testValuesList2 >= testValuesList1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 >= testValuesList1 )
print 'testValuesList2 >= testValuesInst1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 >= testValuesInst1 )
print 'testValuesList2 >= testValuesList2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 >= testValuesList2 )
print 'testValuesList2 >= testValuesInst2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 >= testValuesInst2 )
Testing __ge__: testValuesList1 >= testValuesList1 .... True testValuesList1 >= testValuesInst1 .... True testValuesList1 >= testValuesList2 .... False testValuesList1 >= testValuesInst2 .... False testValuesList2 >= testValuesList1 .... True testValuesList2 >= testValuesInst1 .... True testValuesList2 >= testValuesList2 .... True testValuesList2 >= testValuesInst2 .... True
print 'Testing __gt__:'
print 'testValuesList1 > testValuesList1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 > testValuesList1 )
print 'testValuesList1 > testValuesInst1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 > testValuesInst1 )
print 'testValuesList1 > testValuesList2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 > testValuesList2 )
print 'testValuesList1 > testValuesInst2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 > testValuesInst2 )
print 'testValuesList2 > testValuesList1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 > testValuesList1 )
print 'testValuesList2 > testValuesInst1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 > testValuesInst1 )
print 'testValuesList2 > testValuesList2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 > testValuesList2 )
print 'testValuesList2 > testValuesInst2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 > testValuesInst2 )
Testing __gt__: testValuesList1 > testValuesList1 ..... False testValuesList1 > testValuesInst1 ..... False testValuesList1 > testValuesList2 ..... False testValuesList1 > testValuesInst2 ..... False testValuesList2 > testValuesList1 ..... True testValuesList2 > testValuesInst1 ..... True testValuesList2 > testValuesList2 ..... False testValuesList2 > testValuesInst2 ..... False
print 'Testing __le__:'
print 'testValuesList1 <= testValuesList1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 <= testValuesList1 )
print 'testValuesList1 <= testValuesInst1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 <= testValuesInst1 )
print 'testValuesList1 <= testValuesList2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 <= testValuesList2 )
print 'testValuesList1 <= testValuesInst2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 <= testValuesInst2 )
print 'testValuesList2 <= testValuesList1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 <= testValuesList1 )
print 'testValuesList2 <= testValuesInst1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 <= testValuesInst1 )
print 'testValuesList2 <= testValuesList2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 <= testValuesList2 )
print 'testValuesList2 <= testValuesInst2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 <= testValuesInst2 )
Testing __le__: testValuesList1 <= testValuesList1 .... True testValuesList1 <= testValuesInst1 .... True testValuesList1 <= testValuesList2 .... True testValuesList1 <= testValuesInst2 .... True testValuesList2 <= testValuesList1 .... False testValuesList2 <= testValuesInst1 .... False testValuesList2 <= testValuesList2 .... True testValuesList2 <= testValuesInst2 .... True
print 'Testing __lt__:'
print 'testValuesList1 < testValuesList1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 < testValuesList1 )
print 'testValuesList1 < testValuesInst1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 < testValuesInst1 )
print 'testValuesList1 < testValuesList2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 < testValuesList2 )
print 'testValuesList1 < testValuesInst2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 < testValuesInst2 )
print 'testValuesList2 < testValuesList1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 < testValuesList1 )
print 'testValuesList2 < testValuesInst1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 < testValuesInst1 )
print 'testValuesList2 < testValuesList2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 < testValuesList2 )
print 'testValuesList2 < testValuesInst2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList2 < testValuesInst2 )
Testing __lt__: testValuesList1 < testValuesList1 ..... False testValuesList1 < testValuesInst1 ..... False testValuesList1 < testValuesList2 ..... True testValuesList1 < testValuesInst2 ..... True testValuesList2 < testValuesList1 ..... False testValuesList2 < testValuesInst1 ..... False testValuesList2 < testValuesList2 ..... False testValuesList2 < testValuesInst2 ..... False
print 'Testing __ne__:'
print 'testValuesList1 != testValuesList1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 != testValuesList1 )
print 'testValuesList1 != testValuesInst1 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 != testValuesInst1 )
print 'testValuesList1 != testValuesList2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 != testValuesList2 )
print 'testValuesList1 != testValuesInst2 '.ljust( 39, '.' ) + ' %s' % str(
testValuesList1 != testValuesInst2 )
Testing __ne__: testValuesList1 != testValuesList1 .... False testValuesList1 != testValuesInst1 .... False testValuesList1 != testValuesList2 .... True testValuesList1 != testValuesInst2 .... True
All of the comparison-operator-related magic methods checked out just fine, so there's no need for me to override them.
The irony of this manual, brute-force testing in a series of posts about unit-testing does not escape me. At some point, I'll try to work out a good way to actually unit-test these, perhaps, but that would distract from the goal of today's post, so I'll leave that for later...
Checking the Mutation-operator-related Members
The mutation operators actually make changes to the object
they are being applied to: __add__
and __iadd__
append
members to a list
, so they should also add members, in the
exact same fashion, to a TestValues
instance.
print 'Testing __add__:'
List1 = [ 1, 3 ]
Inst1 = TestValues( emptySource, List1 )
List2 = [ 2, 4 ]
Inst2 = TestValues( emptySource, List2 )
print 'type( List1 ) '.ljust( 39, '.' ) + ' %s' % str(
type( List1 ).__name__ )
print 'type( Inst1 ) '.ljust( 39, '.' ) + ' %s' % str(
type( Inst1 ).__name__ )
print 'List1 + List2 '.ljust( 39, '.' ) + ' %s' % str(
List1 + List2 )
print 'type( List1 + List2 ) '.ljust( 39, '.' ) + ' %s' % str(
type( List1 + List2 ).__name__ )
print 'Inst1 + Inst2 '.ljust( 39, '.' ) + ' %s' % str(
Inst1 + Inst2 )
print 'type( Inst1 + Inst2 ) '.ljust( 39, '.' ) + ' %s' % str(
type( Inst1 + Inst2 ).__name__ )
print 'Inst1 + List2 '.ljust( 39, '.' ) + ' %s' % str(
Inst1 + List2 )
print 'type( Inst1 + List2 ) '.ljust( 39, '.' ) + ' %s' % str(
type( Inst1 + List2 ).__name__ )
Testing __add__:
type( List1 ) ......................... list
type( Inst1 ) ......................... TestValues
List1 + List2 ......................... [1, 3, 2, 4]
type( List1 + List2 ) ................. list
Inst1 + Inst2 ......................... [1, 3, 2, 4]
type( Inst1 + Inst2 ) ................. list
Inst1 + List2 ......................... [1, 3, 2, 4]
type( Inst1 + List2 ) ................. list
So, the short story behind the __add__
method is that it apparently
always returns a list
-instance, whether any of the instances
being added are not list
s themselves or not. That means that I will
need to write a TestValues.__add__
method, in order to return a TestValues
instance.
print 'Testing __iadd__:'
List1 = [ 1, 3 ]
Inst1 = TestValues( emptySource, List1 )
List2 = [ 2, 4 ]
Inst2 = TestValues( emptySource, List2 )
print 'type( List1 ) '.ljust( 39, '.' ) + ' %s' % str(
type( List1 ).__name__ )
print 'type( Inst1 ) '.ljust( 39, '.' ) + ' %s' % str(
type( Inst1 ).__name__ )
List1 += List2
print 'List1 += List2 '.ljust( 39, '.' ) + ' %s' % str(
List1 )
print 'type( List1 += List2 ) '.ljust( 39, '.' ) + ' %s' % str(
type( List1 ).__name__ )
Inst1 += Inst2
print 'Inst1 += Inst2 '.ljust( 39, '.' ) + ' %s' % str(
Inst1 )
print 'type( Inst1 += Inst2 ) '.ljust( 39, '.' ) + ' %s' % str(
type( Inst1 ).__name__ )
List2 += Inst2
print 'List2 += Inst2 '.ljust( 39, '.' ) + ' %s' % str(
List2 )
print 'type( List2 += Inst2 ) '.ljust( 39, '.' ) + ' %s' % str(
type( List2 ).__name__ )
Testing __iadd__: type( List1 ) ......................... list type( Inst1 ) ......................... TestValues List1 += List2 ........................ [1, 3, 2, 4] type( List1 += List2 ) ................ list Inst1 += Inst2 ........................ [1, 3, 2, 4] type( Inst1 += Inst2 ) ................ TestValues List2 += Inst2 ........................ [2, 4, 2, 4] type( List2 += Inst2 ) ................ list
I was expecting __iadd__
to return a TestValues
instance
if the left
item in the applicable code were itself an instance of TestValues
.
I was not expecting a TestValues
instance if the left
value was not a TestValues
instance. In both cases, my expectations
panned out.
That does raise a potential concern, though — In any case where a TestValues
instance is part of a +=
operation, if the result needs to be a TestValues
then the TestValues
instance must be the left
item in the assignment. I don't expect this will be much of a concern, but it does
mean that I may want to give some thought to creating some sort of conversion-method
for lists in order to cast them as TestValues
. It's possible to do so
by simply creating a new TestValues
and passing the list, but that
also requires some awareness of which _UnitTestValuePolicy
is applicable,
or creating a new one just for the new instance. In a case like that, it might be
easier to create an instance-method since the isnstance is already aware
of its _UnitTestValuePolicy
.
By the time I'd finished checking the first two mutation-operator items, I'd
given some thought to the remaining ones (__imul__
, __mul__
and __rmul__
). I couldn't think of a case where I'd actually want
to duplicate the value-list behind a TestValues
instance. Ever. So
those methods will also be overridden — I planned on raising some
Exception
, but hadn't decided which one.
Since I had already planned to override remove
, there was no
checking needed.
The Final TestValues
Implementation
With only four of the inherited magic-method overrides to be written, and one override of another non-magic method, the additions are pretty short:
@describe.AttachDocumentation()
@describe.argument( 'values',
'the values to add to the members',
object )
def __add__( self, values ):
"""
Override of the "+" operator callback for lists. Returns an instance of the
class, populated with the members of the original instance, and with the
provided value appended to it."""
selfValues = list( self )
result = self.__class__( self.ValueSource,
selfValues + values )
return result
@describe.AttachDocumentation()
@describe.raises( RuntimeError,
'if the *= operator is executed against an instance of the class'
)
def __imul__( self, value ):
"""
Override of the "*=" operator callback for lists. Prevents the use of the
operator on instances of the class."""
raise RuntimeError( '%s does not support the "*=" operator' % (
self.__class__.__name__ ) )
@describe.AttachDocumentation()
@describe.raises( RuntimeError,
'if the * operator is executed against an instance of the class'
)
def __mul__( self, value ):
"""
Override of the "*=" operator callback for lists. Prevents the use of the
operator on instances of the class."""
raise RuntimeError( '%s does not support the "*=" operator' % (
self.__class__.__name__ ) )
@describe.AttachDocumentation()
@describe.raises( RuntimeError,
'if the * operator is executed against an instance of the class'
)
def __rmul__( self, value ):
"""
Override of the "*=" operator callback for lists. Prevents the use of the
operator on instances of the class."""
raise RuntimeError( '%s does not support the "*=" operator' % (
self.__class__.__name__ ) )
@describe.AttachDocumentation()
@describe.argument( 'values',
'the value (a single value that is not a list or tuple) or values '
'(a list or tuple of single values that are not lists or tuples) to '
'remove from the members of the instance',
list, tuple, object )
def remove( self, values ):
"""
Removes all instances of the value(s) supplied from the members of the
instance."""
if isinstance( values, ( list, tuple ) ):
for value in values:
while self.count( value ) != 0:
list.remove( self, value )
else:
while self.count( values ) != 0:
list.remove( self, values )
The magic-method overrides are almost brutally simple:
- The implementation of
__add__
renders the instance being added to down to alist
, then returns an instance of the class populated with the results of+
applied to that list plus the values supplied. Simple. It also keeps to the implementation pattern of thelist
type, by retruning a new instance. - The
__imul__
,__mul__
and__rmul__
overrides all raise aRuntimeError
(for lack of a betterException
type to use) if they are called, and the error-messaging explains that the instance doesn't support the operation attempted.
remove
does break, a bit, from the behavior
of list.remove
— list.remove
will raise errors if
the item specified for removal isn't present in the list
to begin with.
After some consideration, and weighing my desire to be able to remove all
instances of a given value from a TestValues
instance, I discarded
that behavior, so a TestValues.remove
shouldn't ever fail because a
value being removed doesn't exist. There may be other implications of this that I
haven't thought of yet, but for now, I'm happy with this implementation: It allows
the removal of all values specified, and accepts a list
or tuple
of values to be removed, as well as single values.
That, then, wraps up the unit_testing
module as far as implementation
is concerned. The next post, and the last one dealing with unit-testing for the time
being, will focus on actually unit-testing the serialization
module — finally.