10

I currently have a unittest.TestCase that looks like..

class test_appletrailer(unittest.TestCase):
    def setup(self):
        self.all_trailers = Trailers(res = "720", verbose = True)

    def test_has_trailers(self):
        self.failUnless(len(self.all_trailers) > 1)

    # ..more tests..

This works fine, but the Trailers() call takes about 2 seconds to run.. Given that setUp() is called before each test is run, the tests now take almost 10 seconds to run (with only 3 test functions)

What is the correct way of caching the self.all_trailers variable between tests?

Removing the setUp function, and doing..

class test_appletrailer(unittest.TestCase):
    all_trailers = Trailers(res = "720", verbose = True)

..works, but then it claims "Ran 3 tests in 0.000s" which is incorrect.. The only other way I could think of is to have a cache_trailers global variable (which works correctly, but is rather horrible):

cache_trailers = None
class test_appletrailer(unittest.TestCase):
    def setUp(self):
        global cache_trailers
        if cache_trailers is None:
            cache_trailers = self.all_trailers = all_trailers = Trailers(res = "720", verbose = True)
        else:
            self.all_trailers = cache_trailers
dbr
  • 165,801
  • 69
  • 278
  • 343

5 Answers5

14

How about using a class member that only gets initialized once?

class test_appletrailer(unittest.TestCase):

    all_trailers = None

    def setup(self):
        # Only initialize all_trailers once.
        if self.all_trailers is None:
            self.__class__.all_trailers = Trailers(res = "720", verbose = True)

Lookups that refer to self.all_trailers will go to the next step in the MRO -- self.__class__.all_trailers, which will be initialized.

cdleary
  • 69,512
  • 53
  • 163
  • 191
  • 2
    How do I destroy `Trailers` when all the class tests are done (assuming I need to)? – Tal Weiss Jan 02 '11 at 08:33
  • 4
    @Tal If you're using Python >=2.7, you can create `all_trailers` in `setUpClass(cls)` and drop the reference to it in `tearDownClass(cls)` classmethods. See http://docs.python.org/library/unittest.html#setupclass-and-teardownclass – akaihola Nov 24 '11 at 08:47
  • I'm not sure if something changed here since '08, but when I try this with the method named "setup" it doesn't seem to get called at all. When I try with the method named "setUp" it runs, but the class member gets reset upon every iteration. I'll update once I find a solution; I really wanted this one to work since it's so clean. – Dan Dec 20 '13 at 03:38
8

An alternative to the proposed solution would be to use a more featured test runner like Nose. With Nose, you can have module-level setup functions which will be run once for a test module. Since it is entirely compatible with unittest, you wouldn't have to change any code.

From the Nose manual:

nose supports fixtures at the package, module, class, and test case level, so expensive initialization can be done as infrequently as possible.

Fixtures are described in detail here. Of course, apart from fulfilling your use-case, I can also strongly recommend it as a general testing tool. None of my projects will leave home without it.

Richard
  • 56,349
  • 34
  • 180
  • 251
Ali Afshar
  • 40,967
  • 12
  • 95
  • 109
  • Good point. I have to add Nose to the list of things to know/try in 2009 :-) – rob Dec 31 '08 at 17:47
  • 1
    It will be 15 minutes well spent. Just try running "nosetests" in the root of any Python project as a starter. It will run your current tests without you changing anything. – Ali Afshar Dec 31 '08 at 17:53
  • to be specific, this would be in a 'setup_class' method. cf: http://somethingaboutorange.com/mrl/projects/nose/1.0.0/writing_tests.html#test-classes – Gregg Lind Jan 13 '11 at 03:00
  • The unittest module in Python 2.7 added class and module fixtures: http://docs.python.org/library/unittest.html#class-and-module-fixtures – akaihola Nov 24 '11 at 08:53
5

If you are using Python >= 2.7 you can use the setUpClass method that is called only once for the whole unit test.

import unittest

from trailers import Trailers

class AppleTrailerTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # only initialize all_trailers once
        cls.all_trailers = Trailers(res='720', verbose=True)

    @classmethod
    def tearDownClass(cls):
        # drop the reference explicitly to let the Trailers object be garbage
        # collected
        cls.all_trailers = None

    def test_one(self):
        # ...

    def test_two(self):
        # ...
akaihola
  • 26,309
  • 7
  • 59
  • 69
chiborg
  • 26,978
  • 14
  • 97
  • 115
1

What is Trailers class doing?
If it holds some state, then you have to reset it each time unit test is being executed.

To solve your problem, I would use a mock object - just emulating the interface of Trailers, providing a faked set of data.


Update: as Trailers is only reading XML data, I would go for a solution like the one proposed by cdleary.

rob
  • 36,896
  • 2
  • 55
  • 65
0

It's worth mentioning that during 2010 the testrunner used in the Zope/Plone world was released as a separate module, unsurprisingly called zope.testrunner. It supports caching setUps in the form of "Layers". Check it out.

Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251