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?

5
  • Something is smelling badly in this scenario. What are you trying to test ? Commented Jul 9, 2013 at 0:49
  • @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. Commented Jul 9, 2013 at 0:52
  • Thanks @fabrizioM. Agree about integration vs unit tests but unfortunately have to work with what is already implemented. Commented Jul 9, 2013 at 1:05
  • 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. Commented Apr 18, 2016 at 13:30
  • @NeilenMarais there is now addClassCleanup() and addModuleCleanup() as of python 3.8 Commented Nov 1, 2023 at 20:31

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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.
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.

Comments

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()

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.