Showing posts with label test policy. Show all posts
Showing posts with label test policy. Show all posts

Thursday, May 11, 2017

Unit-testing vs. Development Disruptions

So, the short story about the disruption in implementing Tag that I alluded to is that I got sidetracked with other things, and couldn't spare the attention to Tag for the best part of two weeks. The specific details why aren't important, though — in a real world, dev-shop environment, similar derailments happen for any number of reasons: Temporary changes in priorities, critical bugs that need attention right now, the list of possible reasons is probably huge. The important part is what the effects of the derailment were:

  • Development was paused mid-stream;
  • It was set aside for a relatively long period, and despite making some efforts to at least try to keep track of where I'd left off, I ended up being away from it for too long to remember what I was thinking at the time;
  • I hadn't gotten through completely documenting (or in a few cases, even loosely defining) many of Tag's members;
  • I still had a fair number of unit-tests that I'd had to defer because of dependencies with other classes;
Note that second point: Normally, if I can, I like to try and at least take some time to scribble down a few simple notes about where I had to leave off, what I was doing, what the immediate concerns were when I had to shelve my efforts, that sort of thing. In this case, though I made an effort to do so, it simply wasn't enough. I'd completely lost track of my thoughts after getting as far as stubbing out all of the members of Tag after finishing the unit-testing of Namespace, minus some dependencies on Tag.

Some of this disruption could have been avoided, maybe, if I'd chosen to build out the concrete classes in a different order. As a result of my decision to take them in the order I did, I was left in the position of waiting to finish the unit-testing of Namespace until I was done with implementing Tag because of some dependencies between the two:

I'd rather get all of the unit-testing resolved at once after Tag is done, and the markup module is more complete.
In retrospect, that was maybe not the best decision I could've made. I haven't gone back yet to look at the interconnections of the markup classes to see if there was a better, less troublesome path I could have taken, so it may well be that there isn't one. I'm going to plan to do that after I finish the module, and if there is anything worthwile that I discover, I'll be sure to post it.

Where That Left Me

As a result, I spent a day or so thrashing about trying to figure out where I'd left off and what my next steps were. Not unlike coming into a project that some other developer has started in a real-word development position, really.

And there is where having solid unit-testing policies and practices came to the rescue, I think. With what had been completed, it was a matter of a few minutes to set up the testTag test-case class and fire off a test-run. That gave me a couple lists of missing test-methods, one for properties of Tag, and one for its methods. It took maybe another half hour or so to stub out all of those test-methods, so that all of them were returning failures, and then another couple of days to work my way through all of the failing tests and get them to pass. All told, there were three types of tasks I had to undertake, guided by those tests:

  • Implement unit-tests on properties and methods that had been completed;
  • Implement unit-tests on properties and methods that were implemented, but also broken in some fashion; and
  • Implement unit-tests that revealed that I hadn't even defined the Tag-member that they were supposed to test, then implement those missing members;
All in all, this process was similar to the sort of thing that is done regularly in TDD shops, if much less formally:
  • I had member-requirements enforced by the test-methods;
  • I had, at least in some cases, functional requirements for those members that were easily converted into usable test-methods;
  • In the cases where I didn't have solid functional requirements, I could refer to the JavaScript API that I was trying to maintain consistency with for most members; and
  • In the (few) remaining cases where I was creating new functionality, I had documented what those members were supposed to do, or had a very good idea what I wanted them to be capable of.
It was still very chaotic (and more than a little frustrating), but it was workable.

Because I was feeling pressed for time, I didn't think to capture a lot of the results of those initial test-runs. In fact, it wasn't until I'd gotten a fair way through them that it occurred to me that what I was going through might be worth posting about, and by that time my results looked like this:

########################################
Unit-test results
########################################
Tests were successful ... False
Number of tests run ..... 219
 + Tests ran in ......... 0.02 seconds
Number of errors ........ 0
Number of failures ...... 92
########################################

By the time Tag was complete, that had grown a bit:

########################################
Unit-test results
########################################
Tests were successful ..... False
Number of tests run ....... 225
 + Tests ran in ........... 0.12 seconds
Number of errors .......... 0
Number of failures ........ 16
Number of tests skipped ... 77
########################################
I'd also decided that I wanted to be able to see both a summary of the number of test-methods that I'd explicitly skipped, and some details about those skipped test-methods. A lot of them were skipped because they were the various getter-, setter- or deleter-methods for propeties that were completely tested in the test-method for the property. I'd also force-skipped the items that were dependent on an implementation of some other class that I hadn't gotten to yet (MarkupParser):
########################################
SKIPPED
#--------------------------------------#
test_DelParent (__main__.testBaseNode)
 - _DelParent is tested in testparent
test_GetParent (__main__.testBaseNode)
 - _GetParent is tested in testparent

...

test_SetParent (__main__.testBaseNode)
 - _SetParent is tested in testparent

...

test_DelinnerHTML (__main__.testTag)
 - ## DEFERRING until MarkupParser is implemented

...

test_GetinnerHTML (__main__.testTag)
 - ## DEFERRING until MarkupParser is implemented

...

test_SetinnerHTML (__main__.testTag)
 - ## DEFERRING until MarkupParser is implemented

...

testcloneNode (__main__.testTag)
 - ## DEFERRING until MarkupParser is implemented
testinnerHTML (__main__.testTag)
 - ## DEFERRING until MarkupParser is implemented

...

########################################
FAILURES

The take-away from this entire story, for me, boiled down to

Having a unit-testing policy, and processes that implement that policy, can help a developer resume their efforts after a disruption as well as ensuring that changes to code didn't break anything.

Even with that, though, Tag still ended up taking longer to finish than I'd expected — an argument, perhaps, for picking the sequence of classes for development more carefully...

Other Things that I Encountered or Thought Of

Here's a potentially-useful trick, with some back-story. BaseNode implements some concrete functionality that's inherited by CDATA, Comment, Tag and Text — I'll use the nextElementSibling property as the example, but there are seven other properties that have the same relationship to the same derived classes. In order to really test those properties, there needs to be a class that has a complete, concrete implementation that derived from BaseNode. Normally, in building out unit-tests for an abstract class like BaseNode, I'd also define a derived class as part of the unit-testing module (BaseNodeDerived, for example), and would use instances of that class as the test-objects in the various test-methods for those concrete items. At some point while I was working through the pile of unit-tests for Tag, it occurred to me that it would be possible (though maybe not desirable in this case) to have one of the test-methods for BaseNode require test-methods in the test-case classes for its derived classes instead. That didn't turn out to be very useful in this case, since BaseNode ended uup being an abstract class with no abstract members (something that I'll have to think on later), but the concept seemed, for a while, to be sound enough that I had code that would do just that:

def testnextElementSibling(self):
    """Unit-tests the nextElementSibling property of a BaseNode instance."""
    # It makes little sense to test here, since that would require 
    # spinning up derived (and potentially broken) classes and there 
    # are *actual* classes where the tests can be run, so require tests
    # in those test-case classes here instead.
    testCases = [ testCDATA, testComment, testTag, testText ]
    testName = 'testnextElementSibling'
    missingCases = [ 
        c.__name__ for c in testCases if not hasattr( c, testName )
    ]
    self.assertEqual( missingCases, [], 
        'testBaseNode requires "%s" test-cases in %s' % 
            ( testName, ', '.join( missingCases ) )
    )
Ultimately, since Tag (and CDATA, Comment and Text) derive from BaseNode, it was possible to build useful test-methods for all of the incomplete BaseNode test-methods using instance of those classes, so this approach wasn't actually needed. Probably just as well: Though I could show that it worked to my satisfaction, it still ended up relying a bit too much on a human making a decision (which items to require tests for, in this case) to maintain some certainty of the code-coverage I'm striving for. That said, I may well come back to that idea, perhaps implementing it as a decorator-method that can be applied to test-case classes, the way AddMethodTesting and AddPropertyTesting a work right now.

A Missing Property Example: ownerDocument

One of the missing members I discovered as a result of the big list of members of Tag was the ownerDocument property. In all honesty, I simply missed implementing, or even requiring it, so the unit-testing approach mentioned above didn't catch it — it was purely human observation and effort that revealed it. Since it was also a common property for all of the BaseNode-derived classes, and something that should be common to all node-types, even if they aren't derived from BaseNode, I required it as an abstract property in IsNode:

#-----------------------------------#
# Abstract Properties               #
#-----------------------------------#

nextElementSibling = abc.abstractproperty()
nextSibling = abc.abstractproperty()
nodeName = abc.abstractproperty()
nodeType = abc.abstractproperty()
ownerDocument = abc.abstractproperty()
parentElement = abc.abstractproperty()
parentNode = abc.abstractproperty()
previousElementSibling = abc.abstractproperty()
previousSibling = abc.abstractproperty()
textContent = abc.abstractproperty()
Once that was in place, though, the unit-testing policies picked up that it was missing immediately raising failures resembling this one, from testCDATA:
########################################
ERRORS
#--------------------------------------#
Traceback (most recent call last):
  File "test_markup.py", line 361, in test__init__
    testObject = CDATA( testValue )
TypeError: 
    Can't instantiate abstract class CDATA with 
      abstract methods ownerDocument

From there, implementing it in BaseNode was simple:

@describe.AttachDocumentation()
def _GetownerDocument( self ):
    """
Returns the top-most IsElement object in the instance's DOM tree"""
    if not self._parent:
        return self
    else:
        return self._parent.ownerDocument
This deviates from the JavaScript ownerDocument, though, and I'm not sure if I'll keep it as is, or alter it later when I get to implementing BaseDocument: In JavaScript, as far as I've ben able to determine, there is no way to create an element that is not a member of a document — even if that element hasn't been attached to the DOM of the document. All parsed tags are automatically document-members, and the createElement method is only available to the document. Taken together, these effectively prevent an element from not being a member of the document they were created in. In the idic framework (so far), it's possible to create Tags even if no document has been defined. I'll have to ponder on that, but for now, I'll let it stand.

Once again, the unit-testing policies picked up that there was still something missing:

#--------------------------------------#
Traceback (most recent call last):
  File "unit_testing.py", line 332, in testMethodCoverage
    target.__name__, missingMethods
AssertionError: 
    Unit-testing policy requires test-methods to be created 
    for all public and protected methods, but testBaseNode 
    is missing the following test-methods: 
        ['test_GetownerDocument']
#--------------------------------------#
Traceback (most recent call last):
  File "unit_testing.py", line 373, in testPropertyCoverage
    'methods: %s' % ( target.__name__, missingMethods )
AssertionError: 
    Unit-testing policy requires test-methods to be created 
    for all public properties, but testBaseNode is missing 
    the following test-methods: 
        ['testownerDocument']
#--------------------------------------#
As annoying as this might seem, it really was a good thing — The unit-testing processes and policies set up back at the beginning of last month were catching that changes had been made, that unit-testing for those changes wasn't complete, and that work needed to be done because of those changes.

That feels like a validation of my unit-testing policies to me.

Two other properties came to my attention while organizing the big Tag members-table: While I'd set up tests for the nodeName and nodeType properties in the testHasTextData test-case class, there were no corresponding tests in testCDATA, testComment or testText.

Again, fixing that didn't take much effort. The example structure for the test-methods for all of the concrete classes looked almost the same as the test-methods for CDATA :

def testnodeName(self):
    """Unit-tests the nodeName property of a CDATA instance."""
    testObject = CDATA( 'test-instance' )
    self.assertEquals( testObject.nodeName, CDATA._nodeName,
        'CDATA does not have a defined _nodeName attribute, or is '
        'inheriting the default None value from HasTextData.'
    )

def testnodeType(self):
    """Unit-tests the nodeType property of a CDATA instance."""
    testObject = CDATA( 'test-instance' )
    self.assertEquals( testObject.nodeType, nodeTypes.CDATASection,
        'CDATA does not have a defined _nodeType attribute, or is '
        'inheriting the default None value from HasTextData.'
    )
Once the underlying class-attributes had been defined for all three concrete classes, and a few other minor things that I noticed that were buried in the 20+ failures from Namespace and BaseNode tests were cleaned up, things were back to a reasonable/expected number of failures and errors.

The moral of this story? Perhaps the idea of embedding the node-types and -names as class properties, then retrieving them with common getter-methods in HasTextData was... too clever, maybe? At the time it felt fairly elegant — Store the actual values in the classes themselves, keeping them nicely encapsulated, etc., etc. But, when push came to shove, the combination of that storage-approach and the coverage-testing routines left a hole that a bug slipped through. It was pure, dumb luck that I happened to notice it when I did.

Getting the Remaining Tests Running

Eventually, after I got done with Tag's implementation and had reconciled all of the expected missing tests, I got to a point where the test-run yielded only a handful of failures:

########################################
Unit-test results
########################################
Tests were successful ..... False
Number of tests run ....... 226
 + Tests ran in ........... 0.14 seconds
Number of errors .......... 0
Number of failures ........ 8
Number of tests skipped ... 77
########################################
Those failures included a variety of items, including:
#--------------------------------------#
testCodeCoverage (__main__.testmarkupCodeCoverage)
AssertionError: 
    Unit-testing policies require test-cases for all classes 
    and functions in the idic.markup module, but the following 
    have not been defined: 
        (testAttributesDict)
#--------------------------------------#
testMethodCoverage (__main__.testNamespace)
AssertionError: 
    Unit-testing policy requires test-methods to be 
    created for all public and protected methods, but 
    testNamespace is missing the following test-methods: 
        ['testGetNamespaceByName', 'testGetNamespaceByURI', 
        'testRegisterNamespace', 
        'test_DelDefaultRenderingModel', 'test_DelName', 
        'test_DelTagRenderingModels', 'test_DelnamespaceURI', 
        'test_GetDefaultRenderingModel', 'test_GetName', 
        'test_GetTagRenderingModels', 'test_GetnamespaceURI', 
        'test_SetDefaultRenderingModel', 'test_SetName', 
        'test_SetTagRenderingModels', 'test_SetnamespaceURI']
#--------------------------------------#
testPropertyCoverage (__main__.testNamespace)
AssertionError: 
    Unit-testing policy requires test-methods to be created 
    for all public properties, but testNamespace is missing 
    the following test-methods: 
        ['testDefaultRenderingModel', 'testName', 
        'testTagRenderingModels', 'testnamespaceURI']
#--------------------------------------#
I handled all of these failures with the normal unit-test-definition process.

Since I was already in a unit-testing frame of mind, I went ahead and dealt with all of the remaining outstanding test-failures that weren't part of Tag's test-case as well. That leaves me with a clean slate, more or less, for the next post, with the following results:

########################################
Unit-test Results: idic.markup
#--------------------------------------#
Tests were SUCCESSFUL
Number of tests run ....... 253
Number of tests skipped ... 95
Tests ran in .......... 0.140 seconds
#--------------------------------------#
########################################
Unit-test Results: idic
#--------------------------------------#
Tests were SUCCESSFUL
Number of tests run ....... 276
Number of tests skipped ... 95
Tests ran in .......... 0.318 seconds
#--------------------------------------#

The unit_testing module had some minor modifications, not much more than some restructuring of the test-results reporting, really, with the addition of the code that counted and displayed details on skipped tests. The current test-results for test_markup is long enough that I didn't want to just dump it into the post, but I still want to share it, so it's downloadable as well.

Tuesday, April 4, 2017

A Unit-testing Walk-through — from the Ground Up

If you've been following this blog for any length of time, it should come as no great surprise to you that I have set up template-files/code-snippets for writing unit-test modules and individual test-cases. I won't show them all at once this time — they will be exposed sufficiently, I think, as I write the tests for the serialization module — but they are written with an eye towards making my normal unit-testing process as smooth and painless as possible:

  • Create a test-module from the UnitTestsTemplate.py file, named test_{module being tested}.py, in a project directory named test_{project name};
  • Set up the various namespace- and module-name-values that the test-module needs to be able to find the source-module being tested;
  • Run the test-module until there are no failed tests.
    • Generate test-case classes to address each failure that stems from a requirement reported from the code-coverage test;
    • Generate test-methods to address each failure that stems from a requirement reported from the tests generated by the AddMethodTesting and AddPropertyTesting decorators;
    • Resolve any other failures reported;
    • Resolve any errors reported;
    • If test-methods cannot be usefully generated, apply the unittest.skip decorator to those.
  • If the test-module should be part of a larger set of tests (say, for an entire package), then add the test-module to the relevant parent module- or package-test file, then run it as above;
Since the serialization module is part of the idic package, the end-result of todays post should be a test_idic.py file that calls the tests from a test_serialization.py file. I'll create the test_serialization.py file first, then work my way up to test_idic.py.

Starting with the UnitTestsTemplate.py file

So, following my process, the first things I need to do are create a copy of the unit-test template file in the appropriate location, and change all of the namespace- and module-name strings in it to match what I'm testing. The most relevant chunk of that happens at the start of the file, which looks like this initially:

#!/usr/bin/env python
"""Defines unit-tests for the module at PackagePath.ModuleName."""

# Python unit-test-module template. Copy the template to a new 
# unit-test-module location, and start replacing names as needed:
#
# PackagePath  ==> The path/namespace of the parent of the module/package 
#                  being tested in this file.
# ModuleName   ==> The name of the module being tested
#
# Then remove this comment-block

#-----------------------------------#
# Standard-library imports.         #
#-----------------------------------#

import os
import sys
import unittest

#-----------------------------------#
# Imports of other third-party      #
# libraries and functionality.      #
#-----------------------------------#

#-----------------------------------#
# idic-library imports.             #
#-----------------------------------#
# - Local development path
sys.path.insert( 1, os.path.expanduser( 
    '~/path/to/local/project/lib/project_name' ) )
# - Installed location
sys.path.insert( 1, '/usr/local/lib/idic' )

from idic.unit_testing import *

#-----------------------------------#
# Import the module being tested    #
#-----------------------------------#
LocalSuite = unittest.TestSuite()

#-----------------------------------#
# Import the module being tested    #
#-----------------------------------#
import PackagePath.ModuleName as ModuleName

#-----------------------------------#
# Code-coverage test-case and       #
# decorator-methods                 #
#-----------------------------------#

class testModuleNameCodeCoverage( moduleCoverageTest ):
    _testNamespace = 'PackagePath'
    _testModule = ModuleName

LocalSuite.addTests( 
    unittest.TestLoader().loadTestsFromTestCase( 
        testModuleNameCodeCoverage
    )
)

#-----------------------------------#
# Test-cases in the module          #
#-----------------------------------#
A couple of quick search-and-replaces in the file are all I need to do to get started:
  • Replacing every instance of PackagePath with the namespace path to the module being tested (in this case, idic, since the full namespace of the serialization module would be idic.serialization); and
  • Replacing every instance of ModuleName with the module name of the module being tested (serialization in this case).
After those replacements (and removing the comments near the top as they instruct), the unit-test module starts with:
#!/usr/bin/env python
"""Defines unit-tests for the module at idic.serialization."""

#-----------------------------------#
# Standard-library imports.         #
#-----------------------------------#

import os
import sys
import unittest

#-----------------------------------#
# Imports of other third-party      #
# libraries and functionality.      #
#-----------------------------------#

#-----------------------------------#
# idic-library imports.             #
#-----------------------------------#
# - Local development path
sys.path.insert( 1, os.path.expanduser( 
    '~/path/to/local/project/lib/project_name' ) )
# - Installed location
sys.path.insert( 1, '/usr/local/lib/idic' )

from idic.unit_testing import *

#-----------------------------------#
# Import the module being tested    #
#-----------------------------------#
LocalSuite = unittest.TestSuite()

#-----------------------------------#
# Import the module being tested    #
#-----------------------------------#
import idic.serialization as serialization

#-----------------------------------#
# Code-coverage test-case and       #
# decorator-methods                 #
#-----------------------------------#

class testserializationCodeCoverage( moduleCoverageTest ):
    _testNamespace = 'idic'
    _testModule = serialization

LocalSuite.addTests( 
    unittest.TestLoader().loadTestsFromTestCase( 
        testserializationCodeCoverage
    )
)

#-----------------------------------#
# Test-cases in the module          #
#-----------------------------------#
Right now, with those changes made, it's executable as long as all the import-paths are correct. Running the test-module generates the following output, which tells what the next steps are:
############################################################
Unit-test results
############################################################
Tests were successful ... False
Number of tests run ..... 2
Number of errors ........ 0
Number of failures ...... 1
############################################################
FAILURES
#----------------------------------------------------------#
Traceback (most recent call last):
  File "unit_testing.py", line 160, in testCodeCoverage
    ', '.join( self._missingTestCases )
AssertionError: Unit-testing policies require test-cases 
    for all classes and functions in the idic.serialization 
    module, but the following have not been defined: 
    (testHasSerializationDict, testIsJSONSerializable, 
    testUnsanitizedJSONWarning)
############################################################
Unit-test results
############################################################
Tests were successful ... False
Number of tests run ..... 2
Number of errors ........ 0
Number of failures ...... 1
############################################################
The output of PrintTestResults includes two copies of the results because the output may well be long enough that the data at the top may be lost off the scrollable area in command-line output. I've toyed with the idea of only generating the end output-information if a certain number of errors or failures has occurred, but that's never really been a high enough priority for me to follow through with it.

That first set of failure-results is nothing more than a list of test-case classes that need to be defined in order for the test-module to provide the required code-coverage.

Creating TestCase Classes

The next step is to generate test-case classes for each of the items noted in the initial failure: testHasSerializationDict, testIsJSONSerializable, and testUnsanitizedJSONWarning. For each of those, I start with the code in my TestCaseTemplate.py file. Removing some of the common helper-methods that use at least occastionally, and the optional set-up and tear-down, there's not a lot to that file:

@testModuleNameCodeCoverage.AddMethodTesting
@testModuleNameCodeCoverage.AddPropertyTesting
class testClassName( unittest.TestCase ):
    """Unit-tests the ClassName class."""

    #--------------------------------------#
    # Unit-tests for class constants,      #
    # if any                               #
    #--------------------------------------#

    #--------------------------------------#
    # Unit-tests the object constructor,   #
    # including any property-values set    #
    # during construction of an instance.  #
    #--------------------------------------#

    def test__init__(self):
        """Unit-tests the initialization of a ClassName instance."""
        self.fail( 'test__init__ is not yet implemented' )

    #--------------------------------------#
    # Unit-tests the object destructor, if #
    # one is provided.                     #
    #--------------------------------------#

#    def test__del__(self):
#        """Unit-tests the destruction of a ClassName instance."""
#        self.fail( 'test__del__ is not yet implemented' )

    #--------------------------------------#
    # Unit-tests of object properties      #
    #--------------------------------------#

#    def testPROPERTYNAME(self):
#        """Unit-tests the PROPERTYNAME property of a ClassName instance."""
#        self.fail( 'testPROPERTYNAME is not yet implemented' )

    #--------------------------------------#
    # Unit-tests of object methods         #
    #--------------------------------------#

#    def testMETHODNAME(self):
#        """Unit-tests the METHODNAME method of a ClassName instance."""
#        self.fail( 'testMETHODNAME is not yet implemented' )


LocalSuite.addTests( 
    unittest.TestLoader().loadTestsFromTestCase( 
        testClassName 
    )
)
Since I usually use template-files rather than snippets, I generally copy the test-case template, paste it into the test-file, then replace all the instances of ClassName with the name of the class that the test-case relates to. The AddMethodTesting and AddPropertyTesting decorators also need to be bound to the initial code-coverage test-case (testserializationCodeCoverage here), in order to avoid any potential requirements contamination across test-modules. By way of example, after adding a test-case to be pointed at HasSerializationDict, the test-module has a test-case the looks like this:
@testserializationCodeCoverage.AddMethodTesting
@testserializationCodeCoverage.AddPropertyTesting
class testHasSerializationDict( unittest.TestCase ):
    """Unit-tests the HasSerializationDict class."""

    #--------------------------------------#
    # Unit-tests for class constants,      #
    # if any                               #
    #--------------------------------------#

    #--------------------------------------#
    # Unit-tests the object constructor,   #
    # including any property-values set    #
    # during construction of an instance.  #
    #--------------------------------------#

    def test__init__(self):
        """Unit-tests the initialization of a HasSerializationDict instance."""
        self.fail( 'test__init__ is not yet implemented' )

    #--------------------------------------#
    # Unit-tests the object destructor, if #
    # one is provided.                     #
    #--------------------------------------#

#    def test__del__(self):
#        """Unit-tests the destruction of a HasSerializationDict instance."""
#        self.fail( 'test__del__ is not yet implemented' )

    #--------------------------------------#
    # Unit-tests of object properties      #
    #--------------------------------------#

#    def testPROPERTYNAME(self):
#        """Unit-tests the PROPERTYNAME property of a HasSerializationDict instance."""
#        self.fail( 'testPROPERTYNAME is not yet implemented' )

    #--------------------------------------#
    # Unit-tests of object methods         #
    #--------------------------------------#

#    def testMETHODNAME(self):
#        """Unit-tests the METHODNAME method of a HasSerializationDict instance."""
#        self.fail( 'testMETHODNAME is not yet implemented' )


LocalSuite.addTests( 
    unittest.TestLoader().loadTestsFromTestCase( 
        testHasSerializationDict 
    )
)
Running the test-module now yields different output (I've removed the end-of-test information for brevity):
############################################################
Unit-test results
############################################################
Tests were successful ... False
Number of tests run ..... 5
Number of errors ........ 0
Number of failures ...... 3
############################################################
FAILURES
#----------------------------------------------------------#
Traceback (most recent call last):
  File "unit_testing.py", line 160, in testCodeCoverage
    ', '.join( self._missingTestCases )
AssertionError: Unit-testing policies require test-cases 
    for all classes and functions in the idic.serialization 
    module, but the following have not been defined: 
    (testIsJSONSerializable, testUnsanitizedJSONWarning)
#----------------------------------------------------------#
Traceback (most recent call last):
  File "unit_testing.py", line 348, in testMethodCoverage
    target.__name__, missingMethods
AssertionError: Unit-testing policy requires test-methods 
    to be created for all public and protected methods, but 
    testHasSerializationDict is missing the following 
    test-methods: 
    ['testFromDict', 'testGetSerializationDict']
#----------------------------------------------------------#
Traceback (most recent call last):
  File "testserialization.py", line 122, in test__init__
    self.fail( 'test__init__ is not yet implemented' )
AssertionError: test__init__ is not yet implemented
############################################################

There are a couple of noteworthy items in this output:

  • The original list of test-cases required has gone down to two items: testIsJSONSerializable and testUnsanitizedJSONWarning, because the testHasSerializationDict test-case class exists now.
  • The testMethodCoverage that was attached by the AddMethodTesting decorator is working, and has identified that two test-methods need to be generated: testFromDict and testGetSerializationDict. Those are both abstract (or at least nominally-abstract) methods, but they live in the scope of the class, and can be usefully tested, I think, so the fact that tests are being required is a good thing in my opinion.
  • Finally, there is a forced failure for the __init__ method of HasSerializationDict.

After adding test-case classes for testIsJSONSerializable and testUnsanitizedJSONWarning, the failures change yet again:

############################################################
Unit-test results
############################################################
Tests were successful ... False
Number of tests run ..... 11
Number of errors ........ 0
Number of failures ...... 6
############################################################
FAILURES
#----------------------------------------------------------#
Traceback (most recent call last):
  File "unit_testing.py", line 348, in testMethodCoverage
    target.__name__, missingMethods
AssertionError: Unit-testing policy requires test-methods 
    to be created for all public and protected methods, 
    but testHasSerializationDict is missing the following 
    test-methods: 
    ['testFromDict', 'testGetSerializationDict']
#----------------------------------------------------------#
Traceback (most recent call last):
  File "testserialization.py", line 122, in test__init__
    self.fail( 'test__init__ is not yet implemented' )
AssertionError: test__init__ is not yet implemented
#----------------------------------------------------------#
Traceback (most recent call last):
  File "unit_testing.py", line 348, in testMethodCoverage
    target.__name__, missingMethods
AssertionError: Unit-testing policy requires test-methods 
    to be created for all public and protected methods, but 
    testIsJSONSerializable is missing the following 
    test-methods: 
    ['testFromJSON', 'testRegisterLoadable', 
    'testSanitizeDict', 'test_GetPythonNamespace', 
    'test_GetSanitizedJSON', 'testwrapjsondump', 
    'testwrapjsondumps', 'testwrapjsonload', 
    'testwrapjsonloads']
#----------------------------------------------------------#
Traceback (most recent call last):
  File "unit_testing.py", line 389, in testPropertyCoverage
    'methods: %s' % ( target.__name__, missingMethods )
AssertionError: Unit-testing policy requires test-methods 
    to be created for all public properties, but 
    testIsJSONSerializable is missing the following 
    test-methods: 
    ['testPythonNamespace', 'testSanitizedJSON']
#----------------------------------------------------------#
Traceback (most recent call last):
  File "testserialization.py", line 174, in test__init__
    self.fail( 'test__init__ is not yet implemented' )
AssertionError: test__init__ is not yet implemented
#----------------------------------------------------------#
Traceback (most recent call last):
  File "testserialization.py", line 226, in test__init__
    self.fail( 'test__init__ is not yet implemented' )
AssertionError: test__init__ is not yet implemented
############################################################

The set-up for the top-level test-module, for the entire idic project-namespace follows the same structure, and uses the same starting-point, but has some minor differences. With all of the empty sections stripped out, this is what it boils down to:

#!/usr/bin/env python
"""Defines unit-tests for the package at idic."""

#-----------------------------------#
# Standard-library imports.         #
#-----------------------------------#

import os
import sys
import unittest

#-----------------------------------#
# idic-library imports.             #
#-----------------------------------#
# - Local development path
sys.path.insert( 1, os.path.expanduser( 
    '~/IDreamInCode/idic/usr/local/lib/idic' ) )
# - Installed location
sys.path.insert( 1, '/usr/local/lib/idic' )

from idic.unit_testing import *

#-----------------------------------#
# Import the module being tested    #
#-----------------------------------#
LocalSuite = unittest.TestSuite()

#-----------------------------------#
# Import the module being tested    #
#-----------------------------------#
import idic

#-----------------------------------#
# Code-coverage test-case and       #
# decorator-methods                 #
#-----------------------------------#

class testidicCodeCoverage( moduleCoverageTest ):
    _testNamespace = 'idic'
    _testModule = idic

LocalSuite.addTests( 
    unittest.TestLoader().loadTestsFromTestCase( 
        testidicCodeCoverage
    )
)

#-----------------------------------#
# Test-cases in the module          #
#-----------------------------------#

#-----------------------------------#
# Child test-cases to run           #
#-----------------------------------#

import test_serialization
LocalSuite.addTests( test_serialization.LocalSuite._tests )

#-----------------------------------#
# Code to execute if file is called #
# or run directly.                  #
#-----------------------------------#
if __name__ == '__main__':
    import time
    results = unittest.TestResult()
    testStartTime = time.time()
    LocalSuite.run( results )
    results.runTime = time.time() - testStartTime
    PrintTestResults( results )
    if not results.errors and not results.failures:
        SaveTestReport( results, 'idic', 
            'idic-test-results.txt' )
The main differences are:
  • The namespace change (idic), allowing it to test the idic package-header file;
  • The lack of test-case classes, because the package-header file for the idic namespace is currently an empty file; and
  • The inclusion of the test_serialization module's test-cases in the LocalSuite test-suite.
Taken together, these allow the test_idic test-module, when executed, to test the idic and idic.serialization namespaces:
###########################################################
Unit-test results
###########################################################
Tests were successful ... True
Number of tests run ..... 23
 + Tests ran in ......... 0.20 seconds
Number of errors ........ 0
Number of failures ...... 0
###########################################################
One additional test runs, the testCodeCoverage provided by the testidicCodeCoverage test-case class. The test_serialization test-module can still be run individually:
###########################################################
Unit-test results
###########################################################
Tests were successful ... True
Number of tests run ..... 22
 + Tests ran in ......... 0.19 seconds
Number of errors ........ 0
Number of failures ...... 0
###########################################################
As long as a similar set-up is put in place for each child module in the idic namespace (importing whichever test_module and adding the LocalSuite._tests from it), the test_idic module can be used to test the entire> idic namespace. Sub-packages inside the idic namespace, if any are eventually built out, can also use the same kind of structure, and will also be included in the tests run by test_idic in those cases.

Creating the Required Test-methods

I had originally planned to go into considerable depth in this post, including a step-by-step walk-through of generating the actual test-methods for all of the classes in the serialization module, and all of the members of those classes. When I'd finished writing it all up, it was a lot longer than I wanted. Also, perhaps because it was about unit-testing, it was really dry stuff.

Long and dry together sounds like a recipe for boring, so I'm going to shelve the discussion of detailed test-method implementation for now. I did generate unit-tests for everything in serialization, though they aren't complete, and if you're curious about them, they can be found in the idic.zip [Snapshot] download at the end of the page.

After I've had some time to think on a better way to present that level of detail, the in-the-weeds unit-testing, I'll come back to those, and probably revisit the current serialization tests.

But I think that's enough on unit-testing for the time being. It gets me where I needed to be to have standard testing policies, and ways to enforce them on my own code. It's also a key component in what I think of as a repeatable build-process — even if that's not a full-on Continuous Integration set-up, it's still in my list of things to accomplish for what I'd consider the minimum viable/bare-bones repeatable build-process:

  • Run automated tests and stop if any tests fail;
  • Generate notifications if the tests fail;
  • Package the build(s) in some fashion so that it's ready to be deployed;
  • Generate notifications if a build fails for reasons other than test-failures; and
  • Deploy to an environment where the current build can be executed.
Since I'm to the point where I need to be able to generate snapshots, it feels to me like it's about time to start looking at that repeatable build process idea.

Thursday, March 30, 2017

Unit-testing and Code-coverage in Python [5]

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 of TestValues rather than a generic list, but that should be the case by default as long as the object being added to is a TestValues 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 of TestValues rather than a generic list, but that should be the case by default as long as the object being added to is a TestValues instance.
__imul__
Called to implement the *= operator. Since this normally yields a number of duplicates of the members of the instance in a list, 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 a list, 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 a list, 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, and TextValues.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.
Over half of these (18 of the 30 items noted) fell into the shouldn't need to be altered category, and only one item solidly fell into the will need to be altered category (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 and list instances.
Mutation-operator-related (also various "magic methods")
__add__ and __iadd__
These methods, relating to the + and += operators as applied to lists, really just needed verification that when a TestValues instance was being added to, that the result was still a TestValues 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 to list instances, though I'm not quite certain when __rmul__ gets called as opposed to __mul__.
Since a TestValues instance is really intended to provide a reasonably-distinct set of values, and these operations effectively duplicate members in the list 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 because str and unicode 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 lists 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 lists 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 a list, 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 the list type, by retruning a new instance.
  • The __imul__, __mul__ and __rmul__ overrides all raise a RuntimeError (for lack of a better Exception type to use) if they are called, and the error-messaging explains that the instance doesn't support the operation attempted.
The override of remove does break, a bit, from the behavior of list.removelist.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.

Tuesday, March 28, 2017

Unit-testing and Code-coverage in Python [4]

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 simple values, 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.