Is it possible to make Nose only run tests which are sub-classes of TestCase or TestSuite (like unittest.main())
-
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?
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)