unittest: Dynamically creating tests

Steven Taschuk staschuk at telusplanet.net
Wed Jun 11 13:53:44 EDT 2003


Quoth Jeremy Bowers:
  [...]
> TypeError: attribute name must be string
> 
> -------------------
> 
> which doesn't make much sense to me.
> 
> My requirement is that the test classes can be generated dynamically; I
> really don't care how they are aggregated or how they are run, as long as
> they *are* run. Does anybody know the best way to do this?

It's possible to arrange your test classes in such a way that
unittest.main would pick them up, but it might be more informative
to see how such test classes could be created and used "by hand":

    import unittest

    def testcasefor(i):
        class MyTest(unittest.TestCase):
            def testMultiplication(self):
                self.assertEquals(3*i, i+i+i)
            def testNegation(self):
                self.assertEquals(0, i + -i)
        return MyTest

    def testsuite():
        suite = unittest.TestSuite()
        for i in range(5) + ['foo']:
            testcase = testcasefor(i)
            tests = unittest.defaultTestLoader.loadTestsFromTestCase(testcase)
            suite.addTest(tests)
        return suite

    if __name__ == '__main__':
        runner = unittest.TextTestRunner()
        runner.run(testsuite())

Note the call to loadTestsFromTestCase.  This creates a suite
containing two instances of MyTest, one for testMultiplication and
one for testNegation.  (Which test method is used in a given
instance is specified by passing its name to TestCase.__init__.
Your exception above is due to instantiating a TestCase yourself
without that argument.)  Note also how to run a TestSuite object
"by hand" by using a TestRunner.

Normally you don't have to worry about any of this, because
unittest.main does all of it for you.

One problem with the above code (as you'll see if you run it and
look at the test failure) is that the error message is less than
adequate -- it doesn't say which value of i was being used in the
test which failed.  One possible remedy is to extend
TestCase.__str__():

        class MyTest(unittest.TestCase):
            # ...
            def __str__(self):
                return '%s (i = %r)' % (unittest.TestCase.__str__(self), i)

A similar thing could be done in shortDescription instead, or in
addition.

> (On a side note, is there a way to programmatically add something to the
> *current* module, while it's being created? [...]

The module object already exists by the time the code is executed;
you just have to get ahold of it.  One method:
    import sys
    setattr(sys.modules[__name__], 'foo', 'bar')
Or, if you don't mind hardcoding the name of your module,
    import mymodule
    setattr(mymodule, 'foo', 'bar')
(Recursive import doesn't recurse infinitely.)

There's also
    globals()['foo'] = 'bar'
and, of course, exec.

  [...]
-- 
Steven Taschuk                                                 o- @
staschuk at telusplanet.net                                      7O   )
                                                               "  (





More information about the Python-list mailing list