在为框架创建测试时,我开始注意以下模式:
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
所以我想以编程方式为这些类型的测试类创建test_feat_*
方法和一个元类。换句话说,对于每个带有签名_test_{featname}(self, arg)
的私有方法,我想要创建两个顶级的,可发现的方法,并创建签名test_{featname}_true(self)
和test_{featname}_false(self)
。
我想出了类似的东西:
#!/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()
我希望输出如下:
this: False
this: True
that: False
that: True
但我得到的是:
$ ./test_meta.py
that: True
.that: True
.that: True
.that: True
.
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
看起来我缺少一些关闭规则。我该如何解决这个问题?有更好的方法吗?
谢谢,
编辑:已修复。请参阅:the snippet。
答案 0 :(得分:5)
确实,这是一个封闭问题:
更改
attrs[testable_name] = lambda self: meth(self, arg)
到
attrs[testable_name] = lambda self,meth=meth,arg=arg: meth(self, arg)
通过使用默认值,lambda中的arg
绑定到循环的每次迭代期间设置的默认值arg
。如果没有默认值,arg
将在循环的所有迭代完成后获取arg
的最后一个值。 (meth
)也是如此。
答案 1 :(得分:1)
我会考虑使用鼻子测试生成器来做这类事情,而不是去元类路线:
http://somethingaboutorange.com/mrl/projects/nose/1.0.0/writing_tests.html#test-generators
测试生成器的缺点是它们是特定于鼻子的特性,因此您需要在stdlib之外引入依赖项。好处是我认为它们更易于编写和理解。