Is it possible to make Nose only run tests which are sub-classes of TestCase or TestSuite (like unittest.main())

StackOverflow https://stackoverflow.com/questions/3110967

  •  29-09-2019
  •  | 
  •  

Question

My test framework is currently based on a test-runner utility which itself is derived from the Eclipse pydev python test-runner. I'm switching to use Nose, which has many of the features of my custom test-runner but seems to be better quality code.

My test suite includes a number of abstract test-classes which previously never ran. The standard python testrunner (and my custom one) only ran instances of unittest.TestCase and unittest.TestSuite.

I've noticed that since I switched to Nose it's running just about anything which starts withthe name "test" which is annoying... because the naming convention we used for the test-mixins also looks like a test class to Nose. Previously these never ran as tests because they were not instances of TestCase or TestSuite.

Obviously I could re-name the methods to exclude the word "test" from their names... that would take a while because the test framework is very big and has a lot of inheritance. On the other hand it would be neat if there was a way to make Nose only see TestCases and TestSuites as being runnable... and nothing else.

Can this be done?

Was it helpful?

Solution

You could try to play with -m option for nosetests. From documentation:

A test class is a class defined in a test module that matches testMatch or is a subclass of unittest.TestCase

-m sets that testMatch, this way you can disable testing anything starting with test.

Another thing is that you can add __test__ = False to your test case class declaration, to mark it “not a test”.

OTHER TIPS

If you want a truly abstract test class, you could just inherit the abstract class from object, then inherit in the testcases later.

For example:

class AbstractTestcases(object):
    def test_my_function_does_something(self):
        self.assertEquals("bar", self.func())

And then use it with:

class TestMyFooFunc(AbstractTestcases, unittest.TestCase):
    def setUp(self):
        self.func = lambda: "foo"

Then nosetests will pick up only the testcases in TestMyFooFunc and not those in AbstractTestCases.

You could use nose's --attr argument to specify an attribute posessed by the unittest.TestCase. For instance, I use:

nosetests --attr="assertAlmostEqual"

You could get even more careful by using and and or matching:

nosetests -A "assertAlmostEqual or addTest"

See unittest's documentation for a full list of methods/attributes, and Nose's description of the capabilities of the --attr plugin.

One addendum to @nailxx 's answer:

You could set __test__ = False in the parent class and then use a metaclass (see This question with some brilliant explanations) to set it back to True when subclassing.

(Finally, I found an excuse to use a metaclass!)

Although __test__ is a double underscore attribute, we have to explicitly set it to True, since not setting it would cause python just to lookup the attribute further up the MRO and evaluate it to False.

Thus, we need to check at class instantiation whether one of the parent classes has __test__ = False. If this is the case and the current class definition has not set __test__ itself, we shall add '__test__': True to the attributes dict.

The resulting code looks like this:

class TestWhenSubclassedMeta(type):
    """Metaclass that sets `__test__` back to `True` when subclassed.

    Usage:

        >>> class GenericTestCase(TestCase, metaclass=TestWhenSubclassed):
        ...     __test__ = False
        ...
        ...     def test_something(self):
        ...         self.fail("This test is executed in a subclass, only.")
        ...
        ...
        >>> class SpecificTestCase(GenericTestCase):
        ...     pass

    """

    def __new__(mcs, name, bases, attrs):
        ATTR_NAME = '__test__'
        VALUE_TO_RESET = False
        RESET_VALUE = True

        values = [getattr(base, ATTR_NAME) for base in bases
                  if hasattr(base, ATTR_NAME)]

        # only reset if the first attribute is `VALUE_TO_RESET`
        try:
            first_value = values[0]
        except IndexError:
            pass
        else:
            if first_value == VALUE_TO_RESET and ATTR_NAME not in attrs:
                attrs[ATTR_NAME] = RESET_VALUE

        return super().__new__(mcs, name, bases, attrs)

One could extend this to some more implicit behaviour like “if the name starts with Abstract, set __test__ = False automatically”, but I for myself would keep the explicit assignment for clarity.


Let me paste simple unittests to demonstrate the behavior – and as a reminder that everybody should take the two minutes to test their code after introducing a feature.

from unittest import TestCase

from .base import TestWhenSubclassedMeta


class SubclassesTestCase(TestCase):
    def test_subclass_resetted(self):
        class Base(metaclass=TestWhenSubclassedMeta):
            __test__ = False

        class C(Base):
            pass

        self.assertTrue(C.__test__)
        self.assertIn('__test__', C.__dict__)

    def test_subclass_not_resetted(self):
        class Base(metaclass=TestWhenSubclassedMeta):
            __test__ = True

        class C(Base):
            pass

        self.assertTrue(C.__test__)
        self.assertNotIn('__test__', C.__dict__)

    def test_subclass_attr_not_set(self):
        class Base(metaclass=TestWhenSubclassedMeta):
            pass

        class C(Base):
            pass

        with self.assertRaises(AttributeError):
            getattr(C, '__test__')

You can also use multiple inheritance on the test case level and let the base class inherit from object only. See this thread:

class MyBase(object):
   def finishsetup(self):
       self.z=self.x+self.y

   def test1(self):
       self.assertEqual(self.z, self.x+self.y)

   def test2(self):
       self.assert_(self.x > self.y)

class RealCase1(MyBase, unittest.TestCase):
   def setUp(self):
       self.x=10
       self.y=5
       MyBase.finishsetup(self)

class RealCase2(MyBase, unittest.TestCase):
   def setUp(self):
       self.x=42
       self.y=13
       MyBase.finishsetup(self) 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top