As I create tests for a framework, I start noticing the following pattern:
class SomeTestCase(unittest.TestCase):
def test_feat_true(self):
_test_feat(self, True)
def test_feat_false(self):
_test_feat(self, False)
def _test_feat(self, arg):
pass # test logic goes here
So I want to programmatically create test_feat_* methods for these type of test classes with a metaclass. In other words, for each private method with signature _test_{featname}(self, arg), I want two top-level, discoverable methods with the signatures test_{featname}_true(self) and test_{featname}_false(self) to be created.
I came up with something like:
#!/usr/bin/env python
import unittest
class TestMaker(type):
def __new__(cls, name, bases, attrs):
callables = dict([
(meth_name, meth) for (meth_name, meth) in attrs.items() if
meth_name.startswith('_test')
])
for meth_name, meth in callables.items():
assert callable(meth)
_, _, testname = meth_name.partition('_test')
# inject methods: test{testname}_{[false,true]}(self)
for suffix, arg in (('false', False), ('true', True)):
testable_name = 'test{0}{1}'.format(testname, suffix)
attrs[testable_name] = lambda self: meth(self, arg)
return type.__new__(cls, name, bases, attrs)
class TestCase(unittest.TestCase):
__metaclass__ = TestMaker
def _test_this(self, arg):
print 'this: ' + str(arg)
def _test_that(self, arg):
print 'that: ' + str(arg)
if __name__ == '__main__':
unittest.main()
I expect some output like:
this: False
this: True
that: False
that: True
But what I got is:
$ ./test_meta.py
that: True
.that: True
.that: True
.that: True
.
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
It looks like there are some closure rules that I am missing. How do I get around this? Is there a better approach?
Thanks,
edit: Fixed. See: the snippet.
Indeed, it is a closure issue:
Change
to
By using a default value,
arginside the lambda is bound to the default valueargset during each iteration of the loop. Without the default value,argtakes on the last value ofargafter all the iterations of the loop have completed. (And the same goes formeth).