Search code examples
pythonreflectionintrospection

Python Introspection: Defining dynamic class methods during runtime


I'm trying to create a unit test, that checks that every function in mymodule has its own TestCase instance.
To reduce boiler-plate code and manual effort I wanted to use introspection/reflection to dynamically add lambda functions as class methods to the initially empty class Test_TestCases.
The following code kind of works - it does indeed add lambdas as class methods and unittest.main() finds and correctly calls them.

import unittest
from unittest import TestCase

import mymodule

class Test_TestCases(TestCase):
"""Class whose test_* methods will be created during runtime."""
    pass

################################################################################

if __name__ == "__main__":
    for item in dir(mymodule):
        attr = getattr(pws, item)
        if callable(attr):
            testname = "Test_%s" % item
            setattr(Test_TestCases, "test_%s_is_tested" % item,
                    lambda self: self.assertTrue(testname in globals()) and
                    issubclass(getattr(globals(), testname), TestCase))
    unittest.main()

The problem is, that all tests succeed even though I do have untested functions in my unit test module.
After a little trying around I figured out that the variable testname has the same value every time the lambda is called.
I could minimize the problem to this piece of code for reproducability:

lambdas = []
for i in range(5):
    lambdas.append(lambda: str(i))
print ", ".join(f() for f in lambdas)

I'd expect this output:

0, 1, 2, 3, 4

but instead I get:

4, 4, 4, 4, 4

It seems that the lambdas are initialized lazily.
Could anyone please explain this behaviour or give me a hint on how I would accomplish my goal properly?

Thanks in advance


Solution

  • Shadow testname with another wrapper function:

    def assertion(testname=testname):
        def function(self):
            return self.assertTrue(testname in globals()) and issubclass(getattr(globals(), testname), TestCase))
    
        return function
    
    setattr(Test_TestCases, "test_%s_is_tested" % item, assertion())