unittest and metaclass: automatic test_* method generation

淺唱寂寞╮ 提交于 2019-12-06 05:13:39

问题


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.


回答1:


Indeed, it is a closure issue:

Change

attrs[testable_name] = lambda self: meth(self, arg)

to

attrs[testable_name] = lambda self,meth=meth,arg=arg: meth(self, arg)

By using a default value, arg inside the lambda is bound to the default value arg set during each iteration of the loop. Without the default value, arg takes on the last value of arg after all the iterations of the loop have completed. (And the same goes for meth).




回答2:


Rather than going the metaclass route I would look into using nose test generators for this sort of thing:

http://somethingaboutorange.com/mrl/projects/nose/1.0.0/writing_tests.html#test-generators

The downside of test generators is that they are a nose specific feature, so you need to introduce a dependency outside the stdlib. The upside is that I think they are easier to write and understand.



来源:https://stackoverflow.com/questions/5175942/unittest-and-metaclass-automatic-test-method-generation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!