With Python unittest, how can I create and use an & ldquo; which returns a test suite & rdquo;

advertisements

I'm learning Python and have been trying to understand more about the details of Python's unittest module. The documentation includes the following:

For the ease of running tests, as we will see later, it is a good idea to provide in each test module a callable object that returns a pre-built test suite:

def suite():
    suite = unittest.TestSuite()
    suite.addTest(WidgetTestCase('testDefaultSize'))
    suite.addTest(WidgetTestCase('testResize'))
    return suite

As far as I can tell, the purpose of doing this is not explained. In addition, I was unable to figure out how one would use such a method. I tried several things without success (aside from learning about the error messages I got):

import unittest

def average(values):
    return sum(values) / len(values)

class MyTestCase(unittest.TestCase):
    def testFoo(self):
        self.assertEqual(average([10,100]),55)

    def testBar(self):
        self.assertEqual(average([11]),11)

    def testBaz(self):
        self.assertEqual(average([20,20]),20)

    def suite():
        suite = unittest.TestSuite()
        suite.addTest(MyTestCase('testFoo'))
        suite.addTest(MyTestCase('testBar'))
        suite.addTest(MyTestCase('testBaz'))
        return suite

if __name__ == '__main__':
    # s = MyTestCase.suite()
    # TypeError: unbound method suite() must be called
    # with MyTestCase instance as first argument

    # s = MyTestCase.suite(MyTestCase())
    # ValueError: no such test method in <class '__main__.MyTestCase'>: runTest

    # s = MyTestCase.suite(MyTestCase('testFoo'))
    # TypeError: suite() takes no arguments (1 given)

The following "worked" but seems awkward and it required that I change the method signature of suite() to 'def suite(self):'.

s = MyTestCase('testFoo').suite()
unittest.TextTestRunner().run(s)


The very first error message you got is meaningful, and explains a lot.

print MyTestCase.suite # <unbound method MyTestCase.suite>

Unbound. It means that you cannot call it unless you bind it to an instance. It's actually the same for MyTestCase.run:

print MyTestCase.run # <unbound method MyTestCase.run>

Maybe for now you don't understand why you can't call suite, but please leave it aside for now. Would you try to call run on the class, like above? Something like:

MyTestCase.run() # ?

Probably not, right? It does not make sense to write this, and it will not work, because run is an instance method, and needs a self instance to work on. Well it appears that Python "understands" suite in the same way it understands run, as an unbound method.

Let's see why:

If you try to put the suite method out of the class scope, and define it as a global function, it just works:

import unittest

def average(values):
    return sum(values) / len(values)

class MyTestCase(unittest.TestCase):
    def testFoo(self):
        self.assertEqual(average([10,100]),55)

    def testBar(self):
        self.assertEqual(average([11]),11)

    def testBaz(self):
        self.assertEqual(average([20,20]),20)

def suite():
    suite = unittest.TestSuite()
    suite.addTest(MyTestCase('testFoo'))
    suite.addTest(MyTestCase('testBar'))
    suite.addTest(MyTestCase('testBaz'))
    return suite

print suite() # <unittest.TestSuite tests=[<__main__.MyTestCase testMethod=testFoo>, <__main__.MyTestCase testMethod=testBar>, <__main__.MyTestCase testMethod=testBaz>]>

But you don't want it out of the class scope, because you want to call MyTestCase.suite()

You probably thought that since suite was sort of "static", or instance-independent, it did not make sense to put a self argument, did you? It's right.

But if you define a method inside a Python class, Python will expect that method to have a self argument as a first argument. Just omitting the self argument does not make your method static automatically. When you want to define a "static" method, you have to use the staticmethod decorator:

@staticmethod
def suite():
    suite = unittest.TestSuite()
    suite.addTest(MyTestCase('testFoo'))
    suite.addTest(MyTestCase('testBar'))
    suite.addTest(MyTestCase('testBaz'))
    return suite

This way Python does not consider MyTestCase as an instance method but as a function:

print MyTestCase.suite # <function suite at 0x...>

And of course now you can call MyTestCase.suite(), and that will work as expected.

if __name__ == '__main__':
    s = MyTestCase.suite()
    unittest.TextTestRunner().run(s) # Ran 3 tests in 0.000s, OK