Tuesday, March 21, 2017

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

Ideally, if possible, I'd very much like for code-coverage testing to be a trivial thing to put in place in a unit-test module. The best-case scenario I can think of would be to have some common unittest.TestCase-derived class that can just be imported into a given unit-test module, and it would take care of running (perhaps even generating) all the coverage-related testing that I want to have in place.

I'm still using the same copy of the serialization module, with an additional concrete class (JSONSerializableStub) added in to assure that I'm testing an interface, and abstract class, and a concrete class. I've also added a BogusFunction function, because I realized that I also wanted to make sure I was forcing testing of module-level functions as well as classes — though I use them rarely, they should also be thoroughly tested...

Testing for Test-Cases

The first task is to gather up all of that example inspect-based code from the last post, and come up with a test-mechanism to assert that all classes and functions present in the module being tested have a corresponding unittest.TestCase test-case class. Since the module being tested is an unknown quantity as far as the definition of the module-coverage-test class is concerned, I'll need to make sure that it's available to the test-case class. I'll accomplish that by making it a class-attribute. The balance of the code for doing all of the examination and generating the final testCodeCoverage test-method looks like this:

class moduleCoverageTest( unittest.TestCase ):
    """
Unit-test that checks to make sure that all classes in the module being tested 
have corresponding test-case classes in the unit-test module where the derived 
class is defined."""

    #-----------------------------------#
    # Default class constants that      #
    # point to the namespace and module #
    # being tested                      #
    #-----------------------------------#
    _testNamespace = None
    _testModule = None

    @classmethod
    def setUpClass( cls ):
        # Get all the classes available in the module
        cls._moduleClasses = inspect.getmembers( 
            cls._testModule, inspect.isclass )
        # Get all the functions available in the module
        cls._moduleFunctions = inspect.getmembers( 
            cls._testModule, inspect.isfunction )
        # Collect all the *LOCAL* items
        cls._testModuleName = cls._testModule.__name__
        # Find and keep track of all of the test-cases that relate to 
        # classes in the module being tested
        cls._classTests = dict(
            [
                ( 'test%s' % m[ 0 ], m[ 1 ] ) 
                for m in cls._moduleClasses
                if m[ 1 ].__module__ == cls._testModuleName
            ]
        )
        # Ditto for the functions in the module being tested
        cls._functionTests = dict(
            [
                ( 'test%s' % m[ 0 ], m[ 1 ] ) 
                for m in cls._moduleFunctions
                if m[ 1 ].__module__ == cls._testModuleName
            ]
        )
        # The list of required test-case class-names is the aggregated 
        # list of all class- and function-test-case-class names
        cls._requiredTestCases = sorted( 
            cls._classTests.keys() + cls._functionTests.keys()
        )
        # Find and keep track of all of the actual test-case classes in 
        # the module the class resides in
        cls._actualTestCases = dict(
            [ 
                item for item in 
                inspect.getmembers( inspect.getmodule( cls ), 
                  inspect.isclass ) 
                if item[ 1 ].__name__[ 0:4 ] == 'test'
                and issubclass( item[ 1 ], unittest.TestCase )
            ]
        )
        # Calculate the missing test-case-class names, for use by 
        # the testCodeCoverage test-method
        cls._missingTestCases = sorted( 
            set( cls._requiredTestCases ).difference( 
              set( cls._actualTestCases.keys() ) ) )

    def testCodeCoverage(self):
        self.assertEquals( [], self._missingTestCases, 
            'Unit-testing policies require test-cases for all classes and '
            'functions in the %s module, but the following have not been '
            'defined: (%s)' % ( 
                self.__class__._testModule.__name__, 
                ', '.join( self._missingTestCases )
            )
        )

There is a fair amount of pre-processing in the setUpClass method of moduleCoverageTest. setUpClass is called during a unit-test run on each class before any of its test* methods, providing a hook for operations that need to take place before any tests are actually run. In this case, I'm doing a fair amount of inspect-based examination of the module that the class resides in, and the module that the test-module is testing, in order to pre-calculate various data that I expect to use in testing for the presence of test-case classes and (later) property- and method-coverage tests. Those various calculations and aggregations should, ideally, happen only once for any given coverage-test, and need to persist across the life of the test-case class, so the setUpClass method seemed a good place to do all that. It does raise the potential concern that classes derived from moduleCoverageTest could override that method, putting the fidelity of code-coverage testing at risk. Normally, I'd be looking pretty seriously at making the moduleCoverageTest class nominally final, but since subclassing it is kind of key to its purpose, that risk is going to remain unmitigated, at least for now.

With this collected/encapsulated into a common, importable class, it becomes very simple to perform the code-coverage test — All that needs to happen is to import the moduleCoverageTest class, then define a subclass of it, with the namespace and source-module set as class properties of that subclass. A bare-bones test-module for the serialization module starts taking shape as:

import unittest
import serialization

from unit_testing import moduleCoverageTest

#-----------------------------------#
# Set up a local test-suite         #
#-----------------------------------#
LocalSuite = unittest.TestSuite()

#-----------------------------------#
# Import the classes, etc., from    #
# the module/package being tested   #
#-----------------------------------#

#-----------------------------------#
# Test-cases in the module          #
#-----------------------------------#
class testModuleCoverage( moduleCoverageTest ):
    _testNamespace = 'idic'
    _testModule = serialization

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

#-----------------------------------#
# Run local unit-tests if the       #
# module is called directly         #
#-----------------------------------#

if __name__ == '__main__':
    results = unittest.TestResult()
    LocalSuite.run( results )

    print results
    if results.errors:
        for testCase, error in results.errors:
            print testCase
            print error
    if results.failures:
        for testCase, failure in results.failures:
            print testCase
            print failure

There are a few items omitted from this listing, in order to keep it short and relevant, but they mostly center around making sure that imports of the module being tested, and of all its members are done. The output is crude, but functional enough for now. Running this test-code yields a single failure:

<unittest.result.TestResult run=1 errors=0 failures=1>
testCodeCoverage (__main__.testModuleCoverage)
Traceback (most recent call last):
  File "unit_testing.py", line 144, in testCodeCoverage

AssertionError: Unit-testing policies require test-cases for 
all classes and functions in the serialization module, but 
the following have not been defined: 
  (testBogusFunction, testHasSerializationDict, 
  testIsJSONSerializable, testJSONSerializableStub, 
  testUnsanitizedJSONWarning)
which, at this point, is exactly what I want. It's identified that there are no test-case classes for any of the serialization classes, and caused the test to fail.

Perfect!

If it's working as expected, then all that need happen to allow the test-case coverage test to pass is to create the missing test-case classes, like so:

#-----------------------------------#
# Test-cases in the module          #
#-----------------------------------#
class testModuleCoverage( moduleCoverageTest ):
    _testNamespace = 'idic'
    _testModule = serialization

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

class testBogusFunction( unittest.TestCase ):
    pass

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

class testHasSerializationDict( unittest.TestCase ):
    pass

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

class testIsJSONSerializable( unittest.TestCase ):
    pass

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

class testJSONSerializableStub( unittest.TestCase ):
    pass

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

class testUnsanitizedJSONWarning( unittest.TestCase ):
    pass

LocalSuite.addTests( 
    unittest.TestLoader().loadTestsFromTestCase( 
        testUnsanitizedJSONWarning
    )
)
With those additional test-cases, even as useless as they currently are, re-running the test-code yields no failures:
<unittest.result.TestResult run=1 errors=0 failures=0>
That part, then, is good to go.

Testing Coverage of Class Members

In similar unit-testing code that I wrote for work, the process that got implemented ended up performing a check for the presence of all test-cases, listing any that were expected but not created across the entire module (just like what's happening in this implementation so far). It then did a similarly-scoped check across all test-cases looking for missing test-methods for properties and methods in the source classes, but because it was searching the entire module, it would only return one failure at a time. While that was workable, and still achieved the amount of code-coverage that I wanted to accomplish, it did get somewhat tedious, having to run the test-suite, find the single reported missing property and/or method test-results, correct that, then re-run again and repeat until there were no more missing test-methods.

I'd consider that to be a viable fall-back position, but I'd much rather have a separate assertion-failure report for each test-case class, and separate assertion-failures for missing property tests and method tests both. Implementing something at that level of granularity is possible, but there are at least two distinct approaches that could be taken, both with their advantages and potential drawbacks.

The first option, to my thinking, would be to subclass the existing unittest.TestCase class, and add in methods for testing whether the class has all of the appropriate test-methods for the properties and methods of the source-class being tested. That's certainly feasible. The drawback to that, as I see it, is that there might well be other reasons to need to subclass unittest.TestCase, and if/when those arise, that would then mean, potentially, subclassing the original property- and method-test-aware subclass of unittest.TestCase. That has the potential of getting unweildy pretty quickly. There's also considerable potential, I think, for introducing unit-testing failures if that sort of scenario were to play out. Nevertheless, it's still a viable option.

Arguably, a better approach would be to have some mechanism that examines the source-classes for properties and methods that should have corresponding test-methods, and coming up with some way to require a test-method that tests the coverage of those test-methods in comparison to the source-class' members. I believe that the ideal approach would be for the existing moduleCoverageTest class to be able to insert property- and method-coverage tests directly into the test-case classes that it's already keeping track of.

Unfortunately, when I tested that approach, although it certainly was possible to add testMethodCoverage and testPropertyCoverage test-methods to the individual test-case classes, those test-methods did not fire — I'm guessing that it's a sequencing issue of some sort, that all of a TestCase-class' test-methods have to be defined in some fashion at the class level, in the code, so that they are members of their respective classes during the interpretation-run of against the code. Even using the same sort of decoration-like approach that I used for overriding the various json-module functions in the serialization module didn't have the desired effect. The test-methods existed, as far as I could tell, but they didn't fire. A cursory examination of the members of unittest.TestCase didn't lead me to believe that there was any way to register or add a new test-method on the fly, though there may well be one that just wasn't apparent to me.

One more possibility occurred to me, and when I tried it out, it apparently worked — another Python decoration approach. Specifically, I found that if I defined decorator functions like so:

def AddPropertyTesting( cls ):
    def testPropertyCoverage( self ):
        self.assertEquals( '%s.testPropertyCoverage called' % cls.__name__, None )
    cls.testPropertyCoverage = testPropertyCoverage
    return cls

def AddMethodTesting( cls ):
    def testMethodCoverage( self ):
        self.assertEquals( '%s.testMethodCoverage called' % cls.__name__, None )
    cls.testMethodCoverage = testMethodCoverage
    return cls
and decorated the test-case classes accordingly:
@AddPropertyTesting
@AddMethodTesting
class testHasSerializationDict( unittest.TestCase ):
    pass

LocalSuite.addTests( 
    unittest.TestLoader().loadTestsFromTestCase( 
        testHasSerializationDict
    )
)
I at least got some potentially-viable results:
testMethodCoverage (__main__.testHasSerializationDict)
Traceback (most recent call last):
  File "unit_testing.py", line 71, in testMethodCoverage

AssertionError: 
  'testHasSerializationDict.testMethodCoverage called' != None

testPropertyCoverage (__main__.testHasSerializationDict)
Traceback (most recent call last):
  File "unit_testing.py", line 65, in testPropertyCoverage

AssertionError: 
  'testHasSerializationDict.testPropertyCoverage called' != None
These don't look like much, but they at least prove that a decorator-based approach might solve the issue. The one remaining missing piece that I needed was a way to determine if the individual test-case classes had been appropriately decorated, which could be managed as an additional test-method of the moduleCoverageTest class:
def testPropertyCoverage( self ):
    for testCaseClassName in self._classTests:
        # Get the source class, the class being tested
        sourceClass = self._classTests[ testCaseClassName ]
        # Get the corresponding test-case class, locally
        testClass = self._actualTestCases[ testCaseClassName ]
        # Determine if the source class has any properties that 
        # should be tested with the AddPropertyTesting decorator
        sourceProps = [
                pt for pt in inspect.getmembers( sourceClass, 
                    inspect.isdatadescriptor )
                if pt[ 0 ][ 0:2 ] != '__'
            ]
        if sourceProps:
            # If there are properties, then require the decorator
            self.assertNotEqual( 
                testClass.__dict__.get( 'testPropertyCoverage' ),
                None, 
                'Unit-testing policy requires that %s implement a '
                'testPropertyCoverage test-method, since %s has '
                'active properties (%s). Please decorate %s with the '
                '@AddPropertyTesting function.' % ( 
                    testClass.__name__, sourceClass.__name__, 
                    ', '.join( sorted( 
                        [ sp[ 0 ] for sp in sourceProps ]
                    ) ), testClass.__name__
                )
            )
That at least allows the test-process to determine that something needs the property- or method-coverage test-decorators, and cause an assertion-failure if they aren't provided:
<unittest.result.TestResult run=2 errors=0 failures=1>
testPropertyCoverage (__main__.testModuleCoverage)
Traceback (most recent call last):
  File "unit_testing.py", line 185, in testPropertyCoverage

AssertionError: Unit-testing policy requires that 
testJSONSerializableStub implement a testPropertyCoverage 
test-method, since JSONSerializableStub has active properties 
  (FieldName1, FieldName2, PythonNamespace, SanitizedJSON). 
Please decorate testJSONSerializableStub with the 
  @AddPropertyTesting function.

After some tinkering, I managed to get the decoration-based process to work rather well, I think. In use, it will probably feel a little weird for a while, but it appears to be surprisingly robust even this early in the game, and it provides much more granular test-results for missing test-methods than the viable fall-back position mentioned at the start of this post — one test each that checks for source-class property- and method-tests in the test-case class, with a nice, itemized list of what's missing for that test-case.

The Final setUpClass method

Support-data for the AddMethodTesting and AddPropertyTesting methods had to be added to setUpClass, providing the sets of expected test-method names for the methods and properties of the source classes. Picking up where it left off above, these additional items are built like this (after the calculation of cls._missingTestCases:

# Calculate the missing test-case-class names, for use by 
# the testCodeCoverage test-method
cls._missingTestCases = sorted( 
    set( cls._requiredTestCases ).difference( 
        set( cls._actualTestCases.keys() ) ) )

# Calculate the property test-case names for all the 
# module's classes
cls._propertyTestsByClass = {}
for testClass in cls._classTests:
    cls._propertyTestsByClass[ testClass ] = set()
    sourceClass = cls._classTests[ testClass ]
    sourceMRO = list( sourceClass.__mro__ )
    sourceMRO.reverse()
    # Get all the item's properties
    properties = [
        member for member in inspect.getmembers( 
            sourceClass, inspect.isdatadescriptor )
        if member[ 0 ][ 0:2 ] != '__'
    ]
    # Create and populate data-structures that keep track of where 
    # property-members originate from, and what their implementation 
    # looks like. Initially populated with None values:
    propSources = {}
    propImplementations = {}
    for name, value in properties:
        propSources[ name ] = None
        propImplementations[ name ] = None
    for memberName in propSources:
        implementation = sourceClass.__dict__.get( memberName )
        if implementation and propImplementations[ memberName ] != implementation:
            propImplementations[ memberName ] = implementation
            propSources[ memberName ] = sourceClass
    cls._propertyTestsByClass[ testClass ] = set(
        [
            'test%s' % key for key in propSources 
            if propSources[ key ] == sourceClass
        ]
    )

# Calculate the method test-case names for all the module's classes
cls._methodTestsByClass = {}
for testClass in cls._classTests:
    cls._methodTestsByClass[ testClass ] = set()
    sourceClass = cls._classTests[ testClass ]
    sourceMRO = list( sourceClass.__mro__ )
    sourceMRO.reverse()
    # Get all the item's methods
    methods = [
        member for member in inspect.getmembers( 
            sourceClass, inspect.ismethod )
    ] + [
        member for member in inspect.getmembers( 
            sourceClass, inspect.isfunction )
    ]
    # Create and populate data-structures that keep track of where 
    # method-members originate from, and what their implementation 
    # looks like. Initially populated with None values:
    methSources = {}
    methImplementations = {}
    for name, value in methods:
        methSources[ name ] = None
        methImplementations[ name ] = None
    for memberName in methSources:
        implementation = sourceClass.__dict__.get( memberName )
        if implementation and methImplementations[ memberName ] != implementation:
            methImplementations[ memberName ] = implementation
            methSources[ memberName ] = sourceClass
    cls._methodTestsByClass[ testClass ] = set(
        [
            'test%s' % key for key in methSources 
            if methSources[ key ] == sourceClass
        ]
    )

In both cases, the new data is populated for all the source-classes being tested. Each class is retrieved, the Method Resolution Order (MRO) for the class is retrieved, and each superclass of the source-class is checked for each member in the source-class. This allows the expected test-cases for any given class to be built around the members that are implemented within the source class only. For example, the JSONSerializableStub class, though it has all of the inherited members from IsJSONSerializable and so on, it only implements the members that are required (as abstract members from the classes it derives from). The test-methods for IsJSONSerializable will be responsible for testing the members present there, and until or unless JSONSerializableStub overrides a non-abstract member of IsJSONSerializable (taking ownership of it, as it were), there's no requirement to test the members inherited from IsJSONSerializable in JSONSerializableStub.

The Final AddMethodTesting method

The final AddMethodTesting decorator-method relies on the main class that it's bound to storing a fair chunk of information about the module being tested, the members of that module, and the members of those members. All that data is still compiled in the test-case class derived from moduleCoverageTest, during its setUpClass method.

@classmethod
def AddMethodTesting( cls, target ):
    if cls.__name__ == 'moduleCoverageTest':
        raise RuntimeError( 'moduleCoverageTest should be extended '
            'into a local test-case class, not used as one directly.' )
    if not cls._testModule:
        raise AttributeError( '%s does not have a _testModule defined '
            'as a class attribute. Check that the decorator-method is '
            'being called from the extended local test-case class, not '
            'from moduleCoverageTest itself.' % ( cls.__name__ ) )
    try:
        if cls._methodTestsByClass:
            populate = False
        else:
            populate = True
    except AttributeError:
        populate = True
    if populate:
        cls.setUpClass()
    def testMethodCoverage( self ):
        requiredTestMethods = cls._methodTestsByClass[ target.__name__ ]
        activeTestMethods = set(
            [
                m[ 0 ] for m in 
                inspect.getmembers( target, inspect.ismethod )
                if m[ 0 ][ 0:4 ] == 'test'
            ]
        )
        missingMethods = sorted( 
            requiredTestMethods.difference( activeTestMethods )
        )
        self.assertEquals( [], missingMethods, 
            'Unit-testing policy requires test-methods to be created for '
            'all public and protected methods, but %s is missing the '
            'following test-methods: %s' % ( 
                target.__name__, missingMethods
            )
        )
    target.testMethodCoverage = testMethodCoverage
    return target

In order to (hopefully) eliminate any possibility of accidentally using the original moduleCoverageTest decorator-methods, a set of checks is performed first, raising errors if the class is moduleCoverageTest, or if no _testModule class-attribute has been specified. Although it should never be needed, I'm also checking for class-attributes created and populated by setUpClass, and explicilty calling setUpClass if those attributes don't exist.

After that, it's all just getting the test-method names expected/required, removing any test-method names that exist, and checking that the remaining set of test-method names is empty. If it is, then all the required methods exist, and the test will pass. If there are any missing test-method names remaining, then the test will fail, and display the missing test-method names as part of its failure message. All of this happens inside a function defined inside the closure of the wrapping class-method, so all of the class-properties of the moduleCoverageTest-derived class are available.

The Final AddPropertyTesting method

Structurally, this is pretty much identical to the AddMethodTesting method already described. The only substantive differences are which data-structure in the owner class is being used as the check-source, and the messaging:

@classmethod
def AddPropertyTesting( cls, target ):
    if cls.__name__ == 'moduleCoverageTest':
        raise RuntimeError( 'moduleCoverageTest should be extended '
            'into a local test-case class, not used as one directly.' )
    if not cls._testModule:
        raise AttributeError( '%s does not have a _testModule defined '
            'as a class attribute. Check that the decorator-method is '
            'being called from the extended local test-case class, not '
            'from moduleCoverageTest itself.' % ( cls.__name__ ) )
    try:
        if cls._propertyTestsByClass:
            populate = False
        else:
            populate = True
    except AttributeError:
        populate = True
    if populate:
        cls.setUpClass()
    def testPropertyCoverage( self ):
        requiredTestMethods = cls._propertyTestsByClass[ target.__name__ ]
        activeTestMethods = set(
            [
                m[ 0 ] for m in 
                inspect.getmembers( target, inspect.ismethod )
                if m[ 0 ][ 0:4 ] == 'test'
            ]
        )
        missingMethods = sorted( 
            requiredTestMethods.difference( activeTestMethods )
        )
        self.assertEquals( [], missingMethods, 
            'Unit-testing policy requires test-methods to be created for '
            'all public properties, but %s is missing the following test-'
            'methods: %s' % ( target.__name__, missingMethods )
        )
    target.testPropertyCoverage = testPropertyCoverage
    return target

Example serialization Test-Cases

Before I break for the day, let me just dump the results of the following test-case classes, with the applicable decoration in place:

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

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

class testBogusFunction( unittest.TestCase ):
    pass

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

# ...

@testSerializationModuleCoverage.AddMethodTesting
@testSerializationModuleCoverage.AddPropertyTesting
class testIsJSONSerializable( unittest.TestCase ):
    pass

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

@testSerializationModuleCoverage.AddMethodTesting
@testSerializationModuleCoverage.AddPropertyTesting
class testJSONSerializableStub( unittest.TestCase ):
    pass

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

# ...

These test-cases, with no test-methods in them at all, yield the following test-failures:

testMethodCoverage (__main__.testIsJSONSerializable)
Traceback (most recent call last):
  File "unit_testing.py", line 283, in testMethodCoverage

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', 
  'test__init__', 'testwrapjsondump', 'testwrapjsondumps', 
  'testwrapjsonload', 'testwrapjsonloads']

testPropertyCoverage (__main__.testIsJSONSerializable)
Traceback (most recent call last):
  File "unit_testing.py", line 324, in testPropertyCoverage

AssertionError: Unit-testing policy requires test-methods 
to be created for all public properties, but 
testIsJSONSerializable is missing the following test-methods: 
  ['testPythonNamespace', 'testSanitizedJSON']

testMethodCoverage (__main__.testJSONSerializableStub)
Traceback (most recent call last):
  File "unit_testing.py", line 283, in testMethodCoverage

AssertionError: Unit-testing policy requires test-methods 
to be created for all public and protected methods, but 
testJSONSerializableStub is missing the following 
test-methods: 
  ['testFromDict', 'testGetSerializationDict', 'test__init__']

testPropertyCoverage (__main__.testJSONSerializableStub)
Traceback (most recent call last):
  File "unit_testing.py", line 324, in testPropertyCoverage

AssertionError: Unit-testing policy requires test-methods 
to be created for all public properties, but 
testJSONSerializableStub is missing the following 
test-methods:
  ['testFieldName1', 'testFieldName2']
As soon as the applicable test-methods are defined (testFieldName1 and testFieldName2 in the last result, for example), those failures go away, ensuring that all the class-members that I require test-methods for have test-methods (even if they don't do anything).

That, I think, pretty solidly answers

How can I identify what code-elements need tested?
In my next post, I'll start considering
What needs to happen in order to test those elements to my satisfaction?

No comments:

Post a Comment