Reputation: 24207
In a test suite I have some code organized as below, context is some persistent object that is deleted when exiting the with
block:
class Test(TestCase):
def test_action1(self):
with create_context() as context:
context.prepare_context()
context.action1()
self.assertTrue(context.check1())
def test_action2(self):
with create_context() as context:
context.prepare_context()
context.action2()
self.assertTrue(context.check2())
It's obvious that code has some repetition of setup boilerplate in both tests, hence I would like to use setUp() and tearDown() methods to factorize that boilerplate.
But I don't know how to extract the with_statement. What I came up with is something like this:
class Test(TestCase):
def setUp(self):
self.context = create_context()
self.context.prepare_context()
def tearDown(self):
del self.context()
def test_action1(self):
self.context.action1()
self.assertTrue(self.context.check1())
def test_action2(self):
self.context.action2()
self.assertTrue(self.context.check2())
But I believe this is not really equivalent when test fails, also having to put an explicit delete in tearDown() doesn't feel right.
What is the correct way to change my with_statement code to setUp() and tearDown() style ?
Upvotes: 2
Views: 287
Reputation: 6189
You might want to use contextlib.ExitStack
for that.
A context manager that is designed to make it easy to programmatically combine other context managers and cleanup functions, especially those that are optional or otherwise driven by input data.
import contextlib
from unittest import TestCase
class Test(TestCase):
def setUp(self) -> None:
stack = contextlib.ExitStack()
self.context = stack.enter_context(create_context()) # create_context is your context manager
self.addCleanup(stack.close)
def test_action1(self):
self.context.prepare_context()
self.context.action1()
self.assertTrue(self.context.check1())
or if you want to have some control over the teardown or use multiple context managers this will be better
import contextlib
from unittest import TestCase
class Test(TestCase):
def setUp(self):
with contextlib.ExitStack() as stack:
self.context = stack.enter_context(create_context()) # create_context is your context manager
self._resource_stack = stack.pop_all()
def tearDown(self):
self._resource_stack.close()
def test_action1(self):
self.context.prepare_context()
self.context.action1()
self.assertTrue(self.context.check1())
Upvotes: 2
Reputation: 689
I'm not 100% sure about the setUp() and tearDown(), but the methods defined in context managers __enter__
and __exit__
sound like they do what you want them to do (just with different names):
class ContextTester():
def __enter__(self):
self.context = create_context()
self.context.prepare_context()
return self.context
def __exit(self, exc_type, exc_value, exc_traceback):
self.context.close()
def Test(TestCase):
def test_action1(self):
with ContextTester() as context:
context.action1()
self.assertTrue(context.check1())
Upvotes: 2