Greg
Greg

Reputation: 2729

python unitest - How to convert a "with" clause to setUp/tearDown

I have a class full of tests that begin with a with clause to set up a complicated set of environments and resources. Each of the with lines is long and repetitive.

I'd like to convert these to a single implementation as setUp() and teardDown()

Essentially I want something like:

    def setUp(self):
        self.unit = # some long initialisation

    def tearDown(self):
        self.unit.__exit__()

For example: imagine this is simplified to use open()

class SomeTest(unittest.TestCase):
    def test_1(self):
        with open('filename', 'r') as f:
            # do testing with f
...
    def test_100(self):
        with open('filename', 'r') as f:
            # do testing with f

I'd convert it to:

  class SomeTest(unittest.TestCase):

    def setUp(self):
        self.f = open("filename', 'r')

    def tearDown(self):
        self.f.close()

    def test_1(self):
        # do testing with self.f
...
    def test_100(self):
        # do testing with self.f

Now this works because file has a common, easily reproduced __exit__ equivalent namely close().

In our case the __exit__ handler is fairly complex, type dependent, and stateful - and (except for test-purposes) we don't want to expose a close() like API (because that adds stateful complexity) and would allow clients not to use the with clause as designed.

Is there some way to invoke __exit__ appropriately? Or integrate the with as clause around each test case?

Upvotes: 0

Views: 63

Answers (2)

Greg
Greg

Reputation: 2729

In the end decorators is probably more elegant that setUp, tearDown (with lots of arguments skipped)

def with_single_unit(fn):
    @functools.wraps(fn)
    def with_single_unit_(self):
        with SingleUnitStage(lotsa_args_skipped).select(lotsa_args_skipped) as unit:
            self.unit = unit
            fn(self)
    return with_single_unit_

class SomeTest(unittest.TestCase):

    @with_single_unit
    def test_a(self):
       # use self.unit

    @with_single_unit
    def test_b(self):
       # use self.unit

    @with_single_unit
    def test_c(self):
       # use self.unit

Upvotes: 1

Kendas
Kendas

Reputation: 2243

As I commented, I would think that calling self.cm.__exit__(None, None, None) in the tearDown method would work just fine.

def tearDown(self):
    self.cm.__exit__(None, None, None)

However, I got to thinking about another pattern you could try:

from contextlib import contextmanager
import unittest

class SomeTest(unittest.TestCase):
    @contextmanager
    def harness(self):
        with open('file.txt', 'r') as f1, open('file2.txt', 'w') as f2:
            try:
                yield f1, f2
            finally:
                pass

    def test_one(self):
        with self.harness() as (f1, f2):
            pass

Upvotes: 1

Related Questions