Long Post
There's a fair length of code in today's post, though it's mostly pretty simple stuff.
Implementation time!
With a set of requirements for the UnitTestValuePolicy
and
TestValues
classes (mostly?) solidified, there's not much more to
discuss before diving in to the implementation-details. I expect this post will
be code-heavy, and possibly quite long. If it gets too long, I'll spend
my next post in actually writing a unit-test module for the
serialization
module — which is how this all got started.
The filtering-action properties will make frequent use of Python's
list
comprehensions, rather than the filter()
function. In my experience, list comprehensions tend to be faster in most cases,
and I've found, after making extensive use of them over the past several years,
that they are often easier for me to write and maintain, rather than generating
a separate free-standing function to perform evaluations of filtering criteria
(which would also then live somewhere else in the code), while still allowing a
certain degree of complexity that is often difficult to create with a lambda
expression.
By way of example, consider this code:
import time
source = range( -1000000, 2000001 )
def isEven( num ):
if num % 2 == 0:
return True
return False
for trialNumber in range( 1,4 ):
filterFuncStart = time.time()
results = filter( isEven, source )
filterFuncRun = time.time() - filterFuncStart
isEven2 = lambda n: n % 2 == 0
filterLambdaStart = time.time()
results = filter( isEven2, source )
filterLambdaRun = time.time() - filterLambdaStart
listCompStart = time.time()
results = [ n for n in source if n % 2 == 0 ]
listCompRun = time.time() - listCompStart
print 'trial-run #%d' % ( trialNumber )
print 'filter w/ function ............. %0.3f sec.' % ( filterFuncRun )
print 'filter w/ lambda ............... %0.3f sec.' % ( filterLambdaRun )
print 'list comp. ..................... %0.3f sec.' % ( listCompRun )
print 'list-comp ÷ filter w/ function ... %0.2f%%' % (
float( int( listCompRun *10000 / filterFuncRun ) / 100.0 ) )
print 'list-comp ÷ filter w/ lambda ..... %0.2f%%' % (
float( int( listCompRun *10000 / filterLambdaRun ) / 100.0 ) )
print
When this is run on my main development laptop, the results are pretty close to
this:
trial-run #1 filter w/ function ................ 0.790 sec. filter w/ lambda .................. 0.652 sec. list comp. ........................ 0.565 sec. list-comp ÷ filter w/ function ... 71.53% list-comp ÷ filter w/ lambda ..... 86.73% trial-run #2 filter w/ function ................ 0.729 sec. filter w/ lambda .................. 0.616 sec. list comp. ........................ 0.538 sec. list-comp ÷ filter w/ function ... 73.79% list-comp ÷ filter w/ lambda ..... 87.39% trial-run #3 filter w/ function ................ 0.725 sec. filter w/ lambda .................. 0.625 sec. list comp. ........................ 0.551 sec. list-comp ÷ filter w/ function ... 76.04% list-comp ÷ filter w/ lambda ..... 88.16%If you're interested in seeing what the timing-difference looks like on your own machine, here's the script-file:
The list-comprehension approach clocks in at somewhere between 70 and 75% of the run-time for an equivalent
filter()
call with a dedicated function,
and 85 to 90% of the run-time of a lambda
-based filter()
equivalent.
The _UnitTestValuePolicy
Class
As a class, _UnitTestValuePolicy
isn't too complex. It's really
just a collection of lists of test-values, organized internally into groups by
(roughly) formal value-types or by purposes served by the members of
those lists of values as applied to unit-testing test-methods. There are a fair
few values in the defaults of an instance of the object, so the code perhaps looks
long, but it's still pretty simple:
@describe.InitClass()
class _UnitTestValuePolicy( object ):
"""
Represents a collection of standard unit-testing test-method values to be
tested."""
#-----------------------------------#
# Class attributes (and instance- #
# attribute default values) #
#-----------------------------------#
_genericObject = object()
_defaults = {
'bools':[ True, False ],
'falseish':[ 0.0, 0, 0L, None, '', u'', False ],
'floats':[ -1.0, 0.0, 1.0, 2.0 ],
'ints':[ -1, 0, 1, 2 ],
'longs':[ -sys.maxint *2 - 1, -1L, 0L, 1L, 2L, sys.maxint *2 ],
'none':[ None ],
'objects':[ _genericObject ],
'strings':[
'',
' ',
'\t',
'\r',
'\n',
'word',
'multiple words',
'A complete sentence,',
'Multiple sentences. Separated with punctuation.',
'String\tcontaining a tab',
'Multiline\nstring',
],
'trueish':[ 1.0, 0.5, 1, 1L, 'a', u'a', _genericObject, True ],
'unicodes':[
u'',
u' ',
u'\t',
u'\r',
u'\n',
u'word',
u'multiple words',
u'A complete sentence,',
u'Multiple sentences. Separated with punctuation.',
u'String\tcontaining a tab',
u'Multiline\nstring',
],
}
#-----------------------------------#
# Instance property-getter methods #
#-----------------------------------#
def _GetAll( self ):
try:
return self._all
except AttributeError:
self._all = []
for key in self._defaults:
self._all += self._defaults[ key ]
return TestValues( self )
#-----------------------------------#
# Instance Properties #
#-----------------------------------#
All = describe.makeProperty( _GetAll, None, None,
'the complete collection of all test-values',
list
)
#-----------------------------------#
# Instance Initializer #
#-----------------------------------#
@describe.AttachDocumentation()
@describe.keywordargs( 'The collection of values to be populated '
'in the instance and made available for unit-testing "good" and '
'"bad" values' )
@describe.keyword( 'bools',
'the values to store as boolean test-values in the instance',
bool,
default=_defaults[ 'bools' ]
)
@describe.keyword( 'falseish',
'the values to store as "false-ish" test-values in '
'the instance',
object,
default=_defaults[ 'falseish' ]
)
@describe.keyword( 'floats',
'the values to store as floating-point-number test-values in '
'the instance',
float,
default=_defaults[ 'floats' ]
)
@describe.keyword( 'ints',
'the values to store as integer test-values in the instance',
int,
default=_defaults[ 'ints' ]
)
@describe.keyword( 'longs',
'the values to store as long-integer test-values in the instance '
'-- by default includes the minimum negative number value '
'available to the system, and the maximum positive value '
'available to the system',
long,
default=_defaults[ 'longs' ]
)
@describe.keyword( 'none',
'the values to store as "null" test-values in the instance',
type( None ),
default=_defaults[ 'none' ]
)
@describe.keyword( 'objects',
'the values to store as generic object test-values in the instance',
object,
default=_defaults[ 'objects' ]
)
@describe.keyword( 'strings',
'the values to store as string test-values in the instance',
str,
default=_defaults[ 'strings' ]
)
@describe.keyword( 'trueish',
'the values to store as "true-ish" test-values in '
'the instance',
object,
default=_defaults[ 'trueish' ]
)
@describe.keyword( 'unicodes',
'the values to store as unicode test-values in the instance',
str,
default=_defaults[ 'unicodes' ]
)
def __init__( self, **values ):
"""
Instance initializer"""
# Call parent initializers, if applicable.
# Set default instance property-values with _Del... methods as needed.
# Set instance property values from arguments if applicable.
# Set _defaults values from **values members, if they are provided
# - bools
bools = values.get( 'bools' )
if bools:
self._defaults[ 'bools' ] = bools
# - falseish
falseish = values.get( 'falseish' )
if falseish:
self._defaults[ 'falseish' ] = falseish
# - floats
floats = values.get( 'floats' )
if floats:
self._defaults[ 'floats' ] = floats
# - ints
ints = values.get( 'ints' )
if ints:
self._defaults[ 'ints' ] = ints
# - longs
longs = values.get( 'longs' )
if longs:
self._defaults[ 'longs' ] = longs
# - none
none = values.get( 'none' )
if none:
self._defaults[ 'none' ] = none
# - objects
objects = values.get( 'objects' )
if objects:
self._defaults[ 'objects' ] = objects
# - strings
strings = values.get( 'strings' )
if strings:
self._defaults[ 'strings' ] = strings
# - trueish
trueish = values.get( 'trueish' )
if trueish:
self._defaults[ 'trueish' ] = trueish
# - unicodes
unicodes = values.get( 'unicodes' )
if unicodes:
self._defaults[ 'unicodes' ] = unicodes
# Other set-up
The actual _UnitTestValuePolicy
class is not explicitly added
to the __all__
list of the unit_testing
module.
Instead, a default instance, UnitTestValuePolicy
is created by the
module that uses the default values of the class for its test-values. A non-default
instance could still be created, it would just require an explicit import of the
_UnitTestValuePolicy
class, and creation of a new instance with
whatever test-values need to be overridden from the default. For example:
from unit_testing import _UnitTestValuePolicy
myTestingValues = {
# Populate this accordingly, using the keywords expected
}
myTestValues = _UnitTestValuePolicy( **myTestingValues )
myOtherTestValues = _UnitTestValuePolicy(
ints=[ -12, -6, -2, -1,
0, 1, 2, 6, 12 ],
longs=[ -12L, -6L, -2L, -1L,
0L, 1L, 2L, 6L, 12L ],
floats=[ -12.0, -6.0, -2.0, -1.0,
0.0, 1.0, 2.0, 6.0, 12.0 ],
)
The intention is to allow both a standard, common set of test-values with minimal
required set-up, that can be applied to the majority of test-method values,
and the ability to customize some or all of the standard values for
specific test-method needs and implementations, without having to do
too much customization of test-method structures and logic. It's inevitable that
some customization of values will be needed, especially as the structures
being tested start having more properties and methods that utilize collections of
values or custom classes. But with less customization needed for the simplevalues, that can be put off for a while.
The other advantage to having a single, standard collection of test-values available and in use as widely as possible is that if (when) a need for a new test-value arises, it can simply be added to the master collection of values, without having to alter each and every test-method. The larger the body of code being tested, the greater an impact this will have from a time-saving standpoint.
_UnitTestValuePolicy
has a single property, All
,
that will gather up and return all of the test-values available to the instance,
collected into a TestValues
instance.
The default UnitTestValuePolicy
Instance
The All
value/TestValues
-instance of the default
_UnitTestValuePolicy
instance is what's actually exposed by the
module as its UnitTestValuePolicy
constant:
# Define a standard UnitTestValuePolicy constant
UnitTestValuePolicy = _UnitTestValuePolicy().All
__all__.append( 'UnitTestValuePolicy' )
That allows the UnitTestValuePolicy
constant to be immediately
usable for generating lists of test-values with all of the filtering actions provided
by any instance of TestValues
.
The TestValues
Class
The important aspects of TestValues
are the various
filtering-action properties. Their set-up as properties is pretty
typical of any class that uses the describe.makeProperty
process
from several
weeks ago — I've already listed all of the properties themselves several
times, but here's a few of them as they were expressed in the code:
# -- Boolean properties------------------#
Boolean = describe.makeProperty( _GetBoolean, None, None,
'the current test-values that evaluate to True or False when used '
'in comparison logic',
list
)
Strict = describe.makeProperty( _GetStrict, None, None,
'the current test-values that are True or False',
list
)
# ...
# -- Numeric properties------------------#
Numeric = describe.makeProperty( _GetNumeric, None, None,
'the current test-values that are numbers',
list
)
Floats = describe.makeProperty( _GetFloats, None, None,
'the current test-values that are float-type numbers',
list
)
Integers = describe.makeProperty( _GetIntegers, None, None,
'the current test-values that are int-type numbers',
list
)
Longs = describe.makeProperty( _GetLongs, None, None,
'the current test-values that are long-type numbers',
list
)
Even = describe.makeProperty( _GetEven, None, None,
'the current test-values that are even numbers (int and '
'long-int types only)',
list
)
Odd = describe.makeProperty( _GetOdd, None, None,
'the current test-values that are odd numbers (int and '
'long-int types only)',
list
)
# ...
# -- Text properties---------------------#
Text = describe.makeProperty( _GetText, None, None,
'the current test-values that are str- or unicode-type text-values',
list
)
Strings = describe.makeProperty( _GetStrings, None, None,
'the current test-values that are str-type text-values',
list
)
# ...
The real magic of these properties, if there is any, is all contained in their
getter-methods...
There are a couple of properties that aren't directly related to filtering
of test-values: All
and ValueSource
. The ValueSource
property is a reference to the _UnitTestValuePolicy
instance that
contains the complete collection of all available test-values, allowing
the individual instances to be able to look at that object and its _defaults
items if necessary. The All
property, then, allows the instances
to acquire the All
value from the original _UnitTestValuePolicy
instance.
@describe.AttachDocumentation()
def _GetAll( self ):
"""
Gets the complete collection of all available test-values"""
return self._valueSource.All
@describe.AttachDocumentation()
def _GetValueSource( self ):
"""
Gets the source of all available standard test-values"""
return self._valueSource
The various Boolean filter-properties are pretty straightforward:
# -- Boolean property-getters------------#
@describe.AttachDocumentation()
def _GetBoolean( self ):
"""
Gets the current test-values that evaluate to True or False when used
in comparison logic"""
checkValues = ( self.ValueSource._defaults[ 'bools' ] +
self.ValueSource._defaults[ 'trueish' ] +
self.ValueSource._defaults[ 'falseish' ]
)
newValues = [ v for v in self if v in checkValues ]
self._checkValues( newValues, 'Boolean' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetStrict( self ):
"""
Gets the current test-values that are True or False"""
newValues = [ v for v in self if v in ( True, False ) ]
self._checkValues( newValues, 'Strict' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetStrictAndNone( self ):
"""
Gets the current test-values that are True, False, or None"""
newValues = [ v for v in self if v in ( True, False, None ) ]
self._checkValues( newValues, 'StrictAndNone' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetStrictAndNumeric( self ):
"""
Gets the current test-values that are True, False, or any numeric
value that is equivalent to True or False"""
newValues = [
v for v in self
if v in ( True, False, 1, 0, 1L, 0L, 1.0, 0.0 )
]
self._checkValues( newValues, 'StrictAndNumeric' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetStrictNumericNone( self ):
"""
Gets the current test-values that are True, False, None, or any numeric
value that is equivalent to True or False"""
newValues = [
v for v in self
if v in ( True, False, None, 1, 0, 1L, 0L, 1.0, 0.0 )
]
self._checkValues( newValues, 'StrictNumericNone' )
return self.__class__( self.ValueSource, newValues )
As noted earlier, these make extensive use of list comprehensions, so as long as those are understood, there's nothing too spectacular about the actual generation of values being returned. All of these getter-methods make use of a protected helper-method to check that the results being returned are going to actually be useful, though:
@describe.AttachDocumentation()
@describe.argument( 'newValues',
'the sequence of values to test',
list )
@describe.argument( 'name',
'the name of the property being tested',
str, unicode )
@describe.raises( TypeError,
'if newValues is not a list'
)
@describe.raises( ValueError,
'if newValues is an empty list'
)
def _checkValues( self, newValues, name ):
if not isinstance( newValues, list ):
raise TypeError( '%s.%s yielded a non-list value' % (
self.__class__.__name__, name ) )
if len( newValues ) == 0:
raise ValueError( '%s.%s yielded an empty list' % (
self.__class__.__name__, name ) )
The thought here is that any filtering-process that yields an empty list of
test-values is going to be invalid in the context of writing a unit-test. As
an example, consider what would happen if a list were generated by using
UnitTestValuePolicy.Even.Odd
— There are, by
definition, no numbers that are both even and odd. Trying to write
a test-case that relied on that is... nonsensical at best, really. The same concern
would arise from mixing filtering of different types, say like
UnitTestValuePolicy.Strings.Negative
.
The boolean
test-value filter-properties are very value-oriented —
boolean values are simple, though, so that's perhaps no great surprise. There's
not a whole lot of variation between True
and False
(and True
-ish and False
-ish) values, so doing direct
filtering based entirely on whether values being filtered are or are not members
of fixed, magic
value-sets makes sense.
The numeric filter-getters are a bit more process-oriented than their boolean
brethren. Numbers and numeric values are more easily processed by examining their
mathematical properties, and that makes the list-comprehensions that generate the
final filtered output both easy to implement and flexible enough to handle any values
that might get added in on the fly, or as overrides to the defaults in the parent
_UnitTestValuePolicy
instance.
# -- Numeric property-getters------------#
@describe.AttachDocumentation()
def _GetNumeric( self ):
"""
Gets the current test-values that are numeric values (float, int or long types)"""
newValues = [
v for v in self
if type( v ) in ( int, float, long )
]
self._checkValues( newValues, 'Numeric' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
@describe.todo( 'Document _GetFloats' )
def _GetFloats( self ):
"""
Gets the current test-values that are float-type numeric values"""
newValues = [
v for v in self
if type( v ) == float
]
self._checkValues( newValues, 'Floats' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetIntegers( self ):
"""
Gets the current test-values that are int-type numeric values"""
newValues = [
v for v in self
if type( v ) == int
]
self._checkValues( newValues, 'Integers' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetLongs( self ):
"""
Gets the current test-values that are long-type numeric values"""
newValues = [
v for v in self
if type( v ) == long
]
self._checkValues( newValues, 'Longs' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetEven( self ):
"""
Gets the current test-values that are even numbers (int and long-int types only)"""
newValues = [
v for v in self
if type( v ) in ( int, long )
and v % 2 == 0
]
self._checkValues( newValues, 'Even' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetNegative( self ):
"""
Gets the current test-values that are negative numeric values"""
newValues = [
v for v in self
if v < 0
]
self._checkValues( newValues, 'Negative' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetNonNegative( self ):
"""
Gets the current test-values that are non-negative numeric values"""
newValues = [
v for v in self
if v >= 0
]
self._checkValues( newValues, 'NonNegative' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetNonPositive( self ):
"""
Gets the current test-values that are non-positive numeric values"""
newValues = [
v for v in self
if v <= 0
]
self._checkValues( newValues, 'NonPositive' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetNonZero( self ):
"""
Gets the current test-values that are non-zero numeric values"""
newValues = [
v for v in self
if v != 0
]
self._checkValues( newValues, 'NonZero' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetOdd( self ):
"""
Gets the current test-values that are odd numbers (int and long-int types only)"""
newValues = [
v for v in self
if type( v ) in ( int, long )
and v % 2 == 1
]
self._checkValues( newValues, 'Odd' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetPositive( self ):
"""
Gets the current test-values that are negative numeric values"""
newValues = [
v for v in self
if v > 0
]
self._checkValues( newValues, 'Positive' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
@describe.todo( 'Document _GetZero' )
def _GetZero( self ):
"""
Gets the current test-values that are numeric values equal to zero"""
newValues = [
v for v in self
if v == 0
]
self._checkValues( newValues, 'Zero' )
return self.__class__( self.ValueSource, newValues )
A couple of mathematical factoids to bear in mind:
- Zero is an even number — If divided by two, there is no remainder;
- Zero is neither positive nor negative;
The various text-type filters have a lost of qualitative
testing involved
once the basic type-filters are taken care of. Those type-based filters, though
are pretty much wht you might expect given the examples of the Floats
,
Integers
and Longs
propeties for numeric values:
# -- Text property-getters---------------#
@describe.AttachDocumentation()
def _GetText( self ):
"""
Gets the current test-values that are str- or unicode-type text-values"""
newValues = [
v for v in self
if type( v ) in ( str, unicode )
]
self._checkValues( newValues, 'Text' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetStrings( self ):
"""
Gets the current test-values that are str-type text-values"""
newValues = [
v for v in self
if type( v ) == str
]
self._checkValues( newValues, 'Strings' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetUnicodes( self ):
"""
Gets the current test-values that are unicode-type text-values"""
newValues = [
v for v in self
if type( v ) == unicode
]
self._checkValues( newValues, 'Unicodes' )
return self.__class__( self.ValueSource, newValues )
Empty- and non-empty text-values are pretty easy:
@describe.AttachDocumentation()
def _GetEmpty( self ):
"""
Gets the current test-values that are empty str- or unicode-values"""
newValues = [
v for v in self
if v == ''
]
self._checkValues( newValues, 'Empty' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetNotEmpty( self ):
"""
Gets the current test-values that are non-empty str- or unicode-values"""
newValues = [
v for v in self
if v != ''
]
self._checkValues( newValues, 'NotEmpty' )
return self.__class__( self.ValueSource, newValues )
Single- and multi-line text-values are also pretty straightforward, though
they rely on an aspect of the split
method of str
and
unicode
values that may not be obvious:
@describe.AttachDocumentation()
def _GetMultiline( self ):
"""
Gets the current test-values that are str- or unicode-type values that have at
least one line-break or carriage-return in them"""
newValues = [
v for v in self
if type( v ) in (str, unicode )
and (
len( v.split( '\n' ) ) > 1
or
len( v.split( '\r' ) ) > 1
)
]
self._checkValues( newValues, 'Multiline' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetSingleLine( self ):
"""
Gets the current test-values that are str- or unicode-type values that have
no line-breaks or carriage-returns in them"""
newValues = [
v for v in self
if type( v ) in ( str, unicode )
and len( v.split( '\n' ) ) == 1
and len( v.split( '\r' ) ) == 1
)
]
self._checkValues( newValues, 'SingleLine' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetNoTabs( self ):
"""
Gets the current test-values that are CRITERIA"""
newValues = [
v for v in self
if type( v ) in (str, unicode )
and len( v.split( '\t' ) ) == 1
]
self._checkValues( newValues, 'NoTabs' )
return self.__class__( self.ValueSource, newValues )
@describe.AttachDocumentation()
def _GetSingleWords( self ):
"""
Gets the current test-values that have no spaces in them"""
newValues = [
v for v in self
if type( v ) in (str, unicode )
and len( v.split( ' ' ) ) == 1
]
self._checkValues( newValues, 'SingleWords' )
return self.__class__( self.ValueSource, newValues )
The same mechanism used for determining whether a text-value has line-breaking
characters can also be used to determine if that value has tabs ('\t'
),
or spaces so I also included the _GetNoTabs
and _GetSingleWords
filter-getter methods, since they use the same basic mechanism...
The perhaps-not-obvious aspect of split()
is that when a text-value
is split on a character that does not exist in the original text, the result is
still a list
, with one member containing the original string. That
is:
print 'This is a single-line string'.split( '\n' )
['This is a single-line string']
print 'This is a \nmulti-line string'.split( '\n' )
['This is a ', 'multi-line string']
The inclusion of both '\n'
and '\r'
is because I
expect that I'll need to be able to check against values that have either or
both new-lines and carriage-returns in them, particularly once I
start delving into HTTP request-response functionality, where the presence of both
in a response has been part of the protocol-standards for quite some time.
At present, that leaves the HasText
and TagName
filter-properties unimplemented:
@describe.AttachDocumentation()
@describe.todo( 'Document _GetHasText' )
@describe.todo( 'Implement _GetHasText' )
def _GetHasText( self ):
"""
Gets the current test-values that are CRITERIA"""
# TODO: Implement me
raise NotImplementedError( '%s.HasText has not been implemented '
'yet' % ( self.__class__.__name__ ) )
@describe.AttachDocumentation()
@describe.todo( 'Document _GetTagName' )
@describe.todo( 'Implement _GetTagName' )
def _GetTagName( self ):
"""
Gets the current test-values that are CRITERIA"""
# TODO: Implement me
raise NotImplementedError( '%s.TagName has not been implemented '
'yet' % ( self.__class__.__name__ ) )
In the time that it's taken to work out all of the other filter-getters, I've
started to question whether _GetHasText
(and its corresponding
HasText
property) are necessary. _GetTagName
and
TagName
I'm going to leave for later (once I start in on markup
handling), and I expect I'll want/need a _GetAttributeName
/AttributeName
filter-getter as well at that point, but I don't see a need for them now.
That covers most of the functionality of both of these classes, though, so even
with the length of the post as it stands here, I'm pretty happy. There is one aspect
to the TestValues
object that I haven't covered, though — since
it's an extension of the built-in list
type, I'll need to take a
look at what needs to be implemented so that it will still behave like a list
while still being a TestValues
.
No comments:
Post a Comment