classification
Title: Add to unittest.TestCase support for using context managers
Type: enhancement Stage:
Components: Library (Lib) Versions: Python 3.4
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: michael.foord Nosy List: Julian, cbc, chris.jerdonek, eli.bendersky, eric.araujo, martin.panter, michael.foord, r.david.murray
Priority: normal Keywords: patch

Created on 2012-07-14 15:33 by chris.jerdonek, last changed 2014-03-20 23:58 by martin.panter.

Files
File name Uploaded Description Edit
issue-15351-concept.patch chris.jerdonek, 2012-07-16 20:53 review
Messages (8)
msg165454 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-07-14 15:33
The setUp() and tearDown() methods of unittest.TestCase are of course extremely useful.  But sometimes one has set up and tear down functionality that one would like to apply in the form of an existing context manager (and that may be from an outside source).

There is currently no clear or clean way to do this.  It would be nice if unittest exposed a way to do this.

The closest I have been able to come is overriding TestCase.run() as follows:

class MyTest(unittest.TestCase):

    def run(self, result=None):
        with my_context_manager() as foo:
            # Do stuff.
            super(MyTest, self).run(result)

But this is not ideal because the context manager is surrounding more than it should (various test initialization code internal to unittest, etc).  Also, ideally the API would let one apply a context manager either before or after setUp() and tearDown(), or both.

Here is one idea for an API.  unittest.TestCase could expose a setUpContext() context manager that wraps the user-defined setUp() and tearDown(), and also a runTestMethod() method that runs the test method code by itself (i.e. currently the following line of unittest/case.py: `self._executeTestPart(testMethod, outcome, isTest=True)`).

To use the API, the user could override a simple method called something like runTest():

    def runTest(self):
        with setUpContext():
            self.runTestMethod()

The user would, in the override, be free to insert additional context managers before or after setUpContext(), or both.
msg165466 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-07-14 20:05
Well, if you want to invoke the context in setup/teardown for some reason (as opposed to in the test methods themselves), you can do this:

  def setUp(self):
      self.foo = MyContextManager.__enter__()
      self.addCleanup(MyContextManager.__exit__())

Personally I rarely do this (except occasionally the mock equivalent, see below), preferring to call the context manager explicitly in the test method itself, often factored out into a test helper.

I think we as a community are still learning how best to use context managers and what the optimum design of context manager is.  There is some thought that a context manager should always provide a non-context way of getting at the functionality of the enter and exit methods.  For example, the mock context managers have start() and stop() methods.  There has also been a small amount of discussion of making more use of context managers in unittest itself, possibly along the lines you suggest.

I think this may be an area in which we are not yet ready to settle on an API.  Michael may have a different opinion, of course ;)
msg165467 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-07-14 20:08
That should have been

   self.addCleanup(MyContextManager.__exit__)

You could alternatively call __exit__() explicitly in tearDown, of course, but I believe addCleanup is a more reliable cleanup than tearDown.
msg165470 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-07-14 20:51
Thanks for the interesting background and feedback. I was aware of the __enter__/__exit__ option but not the other information. And yes, I agree on the importance of trying and discussing any API before settling on it. The one I suggested was merely a point of departure. :)
msg165534 - (view) Author: Michael Foord (michael.foord) * (Python committer) Date: 2012-07-15 16:12
A method on TestCase that *just* executes the test method - allowing for overriding in subclasses - is an interesting idea. Including setUp and tearDown would be harder because TestCase necessarily does a bunch of bookkeeping between each of these steps.
msg165655 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-07-16 20:53
Attached is a patch illustrating the API I suggested for discussion.

To add custom setup and teardown context managers, the user can override the following method:

    def executeTest(self):
        with self.setUpContext():
            self.runTestMethod()

The custom context managers can be placed either before or after the existing setUp/tearDown, or both.

The patch preserves the existing behavior that tearDown() should run only if setUp() was successful, and that doCleanups() should always run.  All tests continue to pass with the patch.
msg168963 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-08-23 20:02
Adding √Čric because of the interest in test setup and tear down in issue 11664.
msg186172 - (view) Author: Julian Berman (Julian) * Date: 2013-04-07 01:25
Now that we have contextlib.ExitStack, I think we should consider that here.

I.e., I think ExitStack deserves a method that calls its __enter__ and __exit__, say .enter() and .exit(), and then the idiom for this wouldn't require anything on TestCase, it'd be:


class TestStuff(TestCase):
    def setUp(self):
        self.stack = ExitStack()

        self.stack.enter_context(my_context_manager())
        self.stack.enter_context(my_context_manager2())
        self.stack.enter_context(my_context_manager3())

        self.stack.enter()
        self.addCleanup(self.stack.exit)
History
Date User Action Args
2014-03-20 23:58:35martin.pantersetnosy: + martin.panter
2013-04-07 01:25:12Juliansetnosy: + Julian
messages: + msg186172
2013-04-02 22:58:22cbcsetnosy: + cbc
2013-02-11 21:04:28michael.foordsetassignee: michael.foord
2012-10-15 13:06:00eli.benderskysetnosy: + eli.bendersky
2012-08-23 20:02:44chris.jerdoneksetnosy: + eric.araujo
messages: + msg168963
2012-07-16 20:53:25chris.jerdoneksetfiles: + issue-15351-concept.patch
keywords: + patch
messages: + msg165655
2012-07-15 16:12:08michael.foordsetmessages: + msg165534
2012-07-14 20:51:54chris.jerdoneksetmessages: + msg165470
2012-07-14 20:08:22r.david.murraysetmessages: + msg165467
2012-07-14 20:05:55r.david.murraysetnosy: + r.david.murray, michael.foord

messages: + msg165466
versions: + Python 3.4
2012-07-14 15:33:51chris.jerdonekcreate