Nose does not scan for tests statically, so you can use metaclass magic to make tests that Nose finds.
The hard part is that standard metaclass techniques don't set the func_name attribute correctly, which is what Nose looks for when checking whether methods on your class are tests.
Here's a simple metaclass. It looks through the func dict and adds a new method for every method it finds, asserting that the method it found has a docstring. These new synthetic methods are given the names "test_%d" %i
.
import new
from inspect import isfunction, getdoc
class Meta(type):
def __new__(cls, name, bases, dct):
newdct = dct.copy()
for i, (k, v) in enumerate(filter(lambda e: isfunction(e[1]), dct.items())):
def m(self, func):
assert getdoc(func) is not None
fname = 'test_%d' % i
newdct[fname] = new.function(m.func_code, globals(), fname,
(v,), m.func_closure)
return super(Meta, cls).__new__(cls, 'Test_'+name, bases, newdct)
Now, let's create a new class that uses this metaclass
class Foo(object):
__metaclass__ = Meta
def greeter(self):
"sdf"
print 'Hello World'
def greeter_no_docstring(self):
pass
At runtime, Foo
will actually be named Test_Foo
and will have greeter
, greeter_no_docstring
, test_1
and test_2
as its methods. When I run nosetests
on this file, here's the output:
$ nosetests -v test.py
test.Test_Foo.test_0 ... FAIL
test.Test_Foo.test_1 ... ok
======================================================================
FAIL: test.Test_Foo.test_0
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Library/Frameworks/EPD64.framework/Versions/7.3/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
self.test(*self.arg)
File "/Users/rmcgibbo/Desktop/test.py", line 10, in m
assert getdoc(func) is not None
AssertionError
----------------------------------------------------------------------
Ran 2 tests in 0.002s
FAILED (failures=1)
This metaclass isn't really useful as is, but if you instead use the Meta
not as a proper metaclass, but as more of a functional metaclass (i.e. takes a class as an argument and returns a new class, one that's renamed so that nose will find it), then it is useful. I've used this approach to automatically test that the docstrings adhere to the Numpy standard as part of a nose test suite.
Also, I've had a lot of trouble getting proper closure working with new.function, which is why this code uses m(self, func)
where func
is made to be a default argument. It would be more natural to use a closure over value
, but that doesn't seem to work.