Spot any errors? let me know, but Unleash your pedant politely please.

Tuesday 26 May 2009

Python : Instantiate nested classes in a superclass, not the direct containing class

I'm using classes for parts of the test harness that I'm writing for some functional testing. I'm writing I want the tests themselves to be fairly clean, fairly free of boilerplate etc, because some of the people writing the test have not done much or any coding before.

I have a TestCase class and I have a TestStep class. When a new test step is written, TestStep is subclassed and included in a subclass of TestCase.

One of the things I wanted to avoid doing was having to get the test writer to instantiate the TestSteps. I don't think this is possible in many languages, but Python lets me do it with its ludicrous ability to introspect and add stuff to objects at runtime.

class NewTestCase(TestCase):
    class NewTestStep(TestStep):
        def body(self):
            ...test step code goes here...
    def body(self):
        stepNewTestStep() #call instance of NewTestStep

Note that there's no stepNewTestStep = NewTestStep() anywhere, and I can add as many TestSteps as I like to the class. stepNewTestStep is not hard coded into the TestCase.

I'd considered naming the NewTestStep objectsas newTestStep rathe than stepNewTestStep, but I was already getting duplicate names in the tests that I'd written, so decided to add the explicit 'step' prefix.

To a Python newbie like me, this is a bit WTF!?, but stick with me. As ever, real Pythoneers, feel free to tell me of better, more Pythonic ways to do this. Here's how it was done…

import types
class TestCase(object):
    def __init__(self):
        for name in dir(self):
            attribute = self.__getattribute__(name)
            if type(attribute) = types.TypeType: # ignore any non classes
                for base in attribute.__bases__:
                    if 'TestStep' in base.__name__: # only interested in TestStep classes
                        setattr(self, 'step%s' % attribute.__name__, attribute())

In plain English, get the names of all the things in TestCase/NewTestCase. For each, make reference to the object of that name (__getattribute__), see if it derived from TestStep and then create a new instance of it (setattr), called 'step[ClassName]' as part of TestCase/NewTestCase.

Note that NewTestCase inherits the __init__ of TestCase, so dir(self) returns the names of everything in NewTestCase.

No comments:

Post a Comment