How can you cleanup a Python UnitTest when setUpClass fails?
Asked Answered
C

4

10

Say I have the following Python UnitTest:

import unittest

def Test(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # Get some resources
        ...

        if error_occurred:
            assert(False)

    @classmethod
    def tearDownClass(cls):
        # release resources
        ...

If the setUpClass call fails, the tearDownClass is not called so the resources are never released. This is a problem during a test run if the resources are required by the next test.

Is there a way to do a clean up when the setUpClass call fails?

Combo answered 9/7, 2013 at 0:43 Comment(5)
Something is smelling badly in this scenario. What are you trying to test ?Birdsong
@fabrizioM: What don't you like about it? I'm running lots of test cases that use some common resources. To reduce the test run time I don't want to set up the common resources every time.Combo
Thanks @fabrizioM. Agree about integration vs unit tests but unfortunately have to work with what is already implemented.Combo
addCleanup() works for per-test cleanup, but I, alas, have not found anything equivalent for class level cleanup. It seems the less elegant try / except recommendations below is the best we can do.Iatrogenic
@Iatrogenic there is now addClassCleanup() and addModuleCleanup() as of python 3.8Ligamentous
O
1

In the meanwhile, addClassCleanup class method has been added to unittest.TestCase for exactly that purpose: https://docs.python.org/3/library/unittest.html#unittest.TestCase.addClassCleanup

Ordure answered 4/1 at 0:23 Comment(0)
B
7

you can put a try catch in the setUpClass method and call directly the tearDown in the except.

def setUpClass(cls):
     try: 
         # setUpClassInner()
     except Exception, e:
        cls.tearDownClass()
        raise # to still mark the test as failed.

Requiring external resources to run your unittest is bad practice. If those resources are not available and you need to test part of your code for a strange bug you will not be able to quickly run it. Try to differentiate Integration tests from Unit Tests.

Birdsong answered 9/7, 2013 at 0:55 Comment(1)
Assuming that everyone using unittest package is implementing actual Unit Tests is not really valid these days. (Or if you're me, this century..) It should be common knowledge now that this package is the King of Misnomers in python core because it is at least as conducive to implementing high level integration or system tests as it is actual unit tests.Jedjedd
V
5

The same way you protect resources elsewhere. try-except:

def setUpClass(cls):
    # ... acquire resources 
    try:
        # ... some call that may fail
    except SomeError, e:
        # cleanup here

Cleanup could be as simple as calling cls.tearDownClass() in your except block. Then you can call assert(False) or whatever method you prefer to exit the test early.

Vaughn answered 9/7, 2013 at 0:55 Comment(0)
I
3

I have a whole bunch of test helper functions that take a test instance and use addCleanup to cleanly setup / tear down threads, temp files etc, so I needed the addCleanup API to work for class level fixtures too. I reimplemented a bit of the unittest doCleanup functionality to help me, and used mock to patch addCleanup() during class setup

import unittest
import logging
import mock

LOGGER = logging.getLogger(__name__)

class ClassCleanupTestCase(unittest.TestCase):
    _class_cleanups = []

    @classmethod
    def setUpClassWithCleanup(cls):
        def cleanup_fn():
            """Do some cleanup!"""
        # Do something that requires cleanup
        cls.addCleanup(cleanup_fn)

    @classmethod
    def addCleanupClass(cls, function, *args, **kwargs):
        cls._class_cleanups.append((function, args, kwargs))

    @classmethod
    def doCleanupsClass(cls):
        results = []
        while cls._class_cleanups:
            function, args, kwargs = cls._class_cleanups.pop()
            try:
                function(*args, **kwargs)
            except Exceptions:
                LOGGER.exception('Exception calling class cleanup function')
                results.append(sys.exc_info())

        if results:
            LOGGER.error('Exception(s) raised during class cleanup, re-raising '
                         'first exception.')
            raise results[0]

    @classmethod
    def setUpClass(cls):
        try:
            with mock.patch.object(cls, 'addCleanup') as cls_addCleanup:
                cls_addCleanup.side_effect = cls.addCleanupClass
                cls.setUpClassWithCleanup()
        except Exception:
            cls.doCleanupsClass()
            raise

    @classmethod
    def tearDownClass(cls):
        cls.doCleanupsClass()
Iatrogenic answered 18/4, 2016 at 14:51 Comment(0)
O
1

In the meanwhile, addClassCleanup class method has been added to unittest.TestCase for exactly that purpose: https://docs.python.org/3/library/unittest.html#unittest.TestCase.addClassCleanup

Ordure answered 4/1 at 0:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.