views:

195

answers:

2

I'm getting some integration tests running against the database, and I'd like to have a structure that looks something like this:

class OracleMixin(object):
    oracle = True
    # ... set up the oracle connection

class SqlServerMixin(object):
    sql_server = True
    # ... set up the sql server connection

class SomeTests(object):
    integration = True
    # ... define test methods here

class test_OracleSomeTests(SomeTests, OracleMixin):
    pass

class test_SqlServerSomeTests(SomeTests, SqlServerMixin):
    pass

This way, I can run SQL Server tests and Oracle tests separately like this:

nosetests -a oracle
nosetests -a sql_server

Or all integration tests like this:

nosetests -a integration

However, it appears that nose will only look for attributes on the subclass, not on the base class. Thus I have to define the test classes like this or the tests won't run:

class test_OracleSomeTests(SomeTests, OracleMixin):
    oracle = True
    integration = True

class test_SqlServerSomeTests(SomeTests, SqlServerMixin):
    sql_server = True
    integration = True

This is a bit tedious to maintain. Any ideas how to get around this? If I was just dealing with one base class, I'd just use a metaclass and define the attributes on each class. But I get an uneasy feeling about having a metaclass for the test class, a metaclass for Oracle, and a metaclass for SQL Server.

+2  A: 

I do not think you can without making your own plugin. The the code in the attrib plugin only looks at the classes __dict__. Here is the code

def wantClass(self, cls):
    """Accept the class if the class or any method is wanted.
    """
    cls_attr = cls.__dict__
    if self.validateAttrib(cls_attr) is not False:
        return None
    ...

You could hack the plugin to do something like (not tested).

def wantClass(self, cls):
    """Accept the class if the class or any method is wanted.
    """
    for class_ in cls.__mro__: 
        cls_attr = class_.__dict__
        if self.validateAttrib(cls_attr) is not False:
            return None
    cls_attr = cls.__dict__
    ...

However, I am not sure that this is better or worse that the metaclass option.

David Raznick
If any of your parent classes are not "new-style" classes (that is, they don't extend "object"), I don't think there will be a `__mro__` attribute. Just a minor tweak, but change the line `for class_ ...`to`for class_ in getattr(cls, '__mro__', []):`
jgeewax
A: 

If you want to find an attribute defined on a parent class, and you have an attribute of the same name in the subclass you will need to add the name of the parent class to access the scope you want

I believe this is what you want:

class Parent:
   prop = 'a property'

   def self_prop(self):
      print self.prop

   # will always print 'a property'
   def parent_prop(self):
      print Parent.prop

class Child(Parent):
   prop = 'child property'

   def access_eclipsed(self):
      print Parent.prop

class Other(Child):
   pass

>>> Parent().self_prop()
"a property"
>>> Parent().parent_prop()
"a property"
>>> Child().self_prop()
"child property"
>>> Child().parent_prop()
"a property"
>>> Child().access_eclipsed()
"a property"
>>> Other().self_prop()
"child property"
>>> Other().parent_prop()
"a property"
>>> Other().access_eclipsed()
"a property"

and in your case it looks like you have two different classes which define different variables so you can just have a try: catch: at the top of your test functions or maybe in the initializer

and say

try:
   isSQLServer = self.sql_server
except AttributeError:
   isSQLServer = False

(though really they should be defining the same variables so that the test class doesn't have to know anything about the subclasses)

Terence Honles