Search code examples
generatornosenosetests

nose test class with generator for multiple tests but only one instance of the class


I am trying to find a way to use nose to run multiple test cases within a class but I need to do so where nose only creates one instance of that class. This class will test a network and the setup of the network takes a few minutes thus the need to run all of the tests via one instance of the class. Here is a basic example of what I am trying to do:

class TestUmbrella(object):

    def __init__(self):
        log.info('__init__ called')

    def run_A(self):
        log.info('Test A is running')

    def run_B(self):
        log.info('Test B is running')

    def run_C(self):
        log.info('Test C is running')

    def run_test(self):
        for x in (self.run_A, self.run_B, self.run_C):
            yield x

This produces:

 2015-03-19 12:22:31,330:   INFO:       tests.l3.FooTest2: __init__ called
 2015-03-19 12:22:31,331:   INFO:       tests.l3.FooTest2: __init__ called
 2015-03-19 12:22:31,331:   INFO:       tests.l3.FooTest2: Test A is running
.2015-03-19 12:22:31,331:   INFO:       tests.l3.FooTest2: __init__ called
 2015-03-19 12:22:31,332:   INFO:       tests.l3.FooTest2: Test B is running
.2015-03-19 12:22:31,332:   INFO:       tests.l3.FooTest2: __init__ called
 2015-03-19 12:22:31,332:   INFO:       tests.l3.FooTest2: Test C is running
.
----------------------------------------------------------------------
Ran 3 tests in 0.002s

OK

What I would like to see is:

 2015-03-19 12:22:31,330:   INFO:       tests.l3.FooTest2: __init__ called
 2015-03-19 12:22:31,331:   INFO:       tests.l3.FooTest2: Test A is running
 2015-03-19 12:22:31,332:   INFO:       tests.l3.FooTest2: Test B is running
 2015-03-19 12:22:31,332:   INFO:       tests.l3.FooTest2: Test C is running

Any ideas on how to get nose to do this?


Solution

  • Two ways to get what you want: Use unittest.TestCase class with setUpClass for your TestUmbrella:

    from unittest import TestCase
    import logging as log
    
    class TestUmbrella(TestCase):
        @classmethod
        def setUpClass(cls):
            log.info('__init__ called')
    
        def run_A_test(self):
            log.info('Test A is running')
    
        def run_B_test(self):
            log.info('Test B is running')
    
        def run_C_test(self):
            log.info('Test C is running')
    

    Note that you will no longer be able yield things on the fly, and would have to rename methods to comply with nose test pattern. That will give you:

    $ nosetests cls_test.py -v
    INFO:root:__init__ called
    run_A_test (cls_test.TestUmbrella) ... INFO:root:Test A is running
    ok
    run_B_test (cls_test.TestUmbrella) ... INFO:root:Test B is running
    ok
    run_C_test (cls_test.TestUmbrella) ... INFO:root:Test C is running
    ok
    
    ----------------------------------------------------------------------
    Ran 3 tests in 0.007s
    
    OK
    

    Alternatively, you can just inject your setup method into the class, but not as part of class initialization:

    import logging as log
    
    class TestUmbrella(object):
        def my_setup(self):
            log.info('__init__ called')
    
        def run_A(self):
            log.info('Test A is running')
    
        def run_B(self):
            log.info('Test B is running')
    
        def run_C(self):
            log.info('Test C is running')
    
        def run_test(self):
            self.my_setup()
            for x in (self.run_A, self.run_B, self.run_C):
                yield x
    

    Finally, if you really cannot offload heavy logic from the constructor, you can run your tests from a standalone function like this:

    def run_test():
        tu = TestUmbrella()
        for x in (tu.run_A, tu.run_B, tu.run_C):
            yield x