我发现我在 Python 中使用了大量的上下文管理器。然而,我一直在使用它们测试许多东西,并且我经常需要以下内容:
class MyTestCase(unittest.TestCase):
def testFirstThing(self):
with GetResource() as resource:
u = UnderTest(resource)
u.doStuff()
self.assertEqual(u.getSomething(), 'a value')
def testSecondThing(self):
with GetResource() as resource:
u = UnderTest(resource)
u.doOtherStuff()
self.assertEqual(u.getSomething(), 'a value')
当这进行许多测试时,这显然会变得无聊,因此本着 SPOT/DRY(单点真理/不要重复),我想将这些位重构到测试 setUp()
和 tearDown()
方法中。
然而,尝试这样做却导致了这种丑陋:
def setUp(self):
self._resource = GetSlot()
self._resource.__enter__()
def tearDown(self):
self._resource.__exit__(None, None, None)
必须有更好的方法来做到这一点。理想情况下,在 setUp()
/tearDown()
中,每个测试方法都没有重复位(我可以看到在每个方法上重复装饰器如何做到这一点)。
编辑:将待测对象视为内部对象,并将 GetResource
对象视为第三方对象(我们不会更改)。
我在这里将 GetSlot
重命名为 GetResource
(这比具体情况更普遍),其中上下文管理器是对象进入锁定状态和退出锁定状态的方式。
I am finding that I am using plenty of context managers in Python. However, I have been testing a number of things using them, and I am often needing the following:
class MyTestCase(unittest.TestCase):
def testFirstThing(self):
with GetResource() as resource:
u = UnderTest(resource)
u.doStuff()
self.assertEqual(u.getSomething(), 'a value')
def testSecondThing(self):
with GetResource() as resource:
u = UnderTest(resource)
u.doOtherStuff()
self.assertEqual(u.getSomething(), 'a value')
When this gets to many tests, this is clearly going to get boring, so in the spirit of SPOT/DRY (single point of truth/dont repeat yourself), I'd want to refactor those bits into the test setUp()
and tearDown()
methods.
However, trying to do that has lead to this ugliness:
def setUp(self):
self._resource = GetSlot()
self._resource.__enter__()
def tearDown(self):
self._resource.__exit__(None, None, None)
There must be a better way to do this. Ideally, in the setUp()
/tearDown()
without repetitive bits for each test method (I can see how repeating a decorator on each method could do it).
Edit: Consider the undertest object to be internal, and the GetResource
object to be a third party thing (which we aren't changing).
I've renamed GetSlot
to GetResource
here—this is more general than specific case—where context managers are the way which the object is intended to go into a locked state and out.
发布评论
评论(6)
如下所示重写
unittest.TestCase.run()
怎么样?这种方法不需要调用任何私有方法或对每个方法执行某些操作,这正是提问者想要的。如果您想修改其中的
TestCase
实例,此方法还允许将TestCase
实例传递到上下文管理器。How about overriding
unittest.TestCase.run()
as illustrated below? This approach doesn't require calling any private methods or doing something to every method, which is what the questioner wanted.This approach also allows passing the
TestCase
instance to the context manager, if you want to modify theTestCase
instance there.如果所有资源获取都成功,则在您不希望
with
语句清理内容的情况下操作上下文管理器是contextlib.ExitStack()
旨在处理。例如(使用
addCleanup()
而不是自定义tearDown()
实现):这是最可靠的方法,因为它可以正确处理多个资源的获取:
这里,如果 < code>GetOtherResource() 失败,第一个资源将被 with 语句立即清理,而如果成功,
pop_all()
调用将推迟清理,直到注册的清理函数运行。如果您知道只会管理一个资源,则可以跳过 with 语句:
但是,这更容易出错,因为如果您在不首先切换到基于 with 语句的版本的情况下向堆栈添加更多资源,如果后续资源获取失败,成功分配的资源可能无法及时清理。
您还可以使用自定义
tearDown()
实现编写类似的内容,方法是在测试用例上保存对资源堆栈的引用:或者,您还可以定义一个通过闭包访问资源的自定义清理函数参考,避免纯粹出于清理目的而需要在测试用例上存储任何额外状态:
Manipulating context managers in situations where you don't want a
with
statement to clean things up if all your resource acquisitions succeed is one of the use cases thatcontextlib.ExitStack()
is designed to handle.For example (using
addCleanup()
rather than a customtearDown()
implementation):That's the most robust approach, since it correctly handles acquisition of multiple resources:
Here, if
GetOtherResource()
fails, the first resource will be cleaned up immediately by the with statement, while if it succeeds, thepop_all()
call will postpone the cleanup until the registered cleanup function runs.If you know you're only ever going to have one resource to manage, you can skip the with statement:
However, that's a bit more error prone, since if you add more resources to the stack without first switching to the with statement based version, successfully allocated resources may not get cleaned up promptly if later resource acquisitions fail.
You can also write something comparable using a custom
tearDown()
implementation by saving a reference to the resource stack on the test case:Alternatively, you can also define a custom cleanup function that accesses the resource via a closure reference, avoiding the need to store any extra state on the test case purely for cleanup purposes:
pytest
装置非常接近您的想法/风格,并且完全满足您的需求:pytest
fixtures are very close to your idea/style, and allow for exactly what you want:看来这个讨论10年后仍然有意义!要添加到 @ncoghlan 的出色答案,看起来
unittest.TestCase
通过从 python 3.11 开始,enterContext
辅助方法!来自 文档:看起来这不需要手动
addCleanup()
来关闭上下文管理器堆栈,因为它是在您向enterContext
提供上下文管理器时添加的。所以看来现在所需要的就是:(我猜
unittest
已经厌倦了每个人都涌向pytest
因为他们有用的装置)Looks like this discussion is still relevant 10 years later! To add to @ncoghlan's excellent answer it looks like
unittest.TestCase
added this exact functionality via theenterContext
helper method as of python 3.11! From the docs:It looks like this precludes the need to manually
addCleanup()
to close the stack of context managers, as it's added when you provide the context manager toenterContext
. So it seems like all that's needed nowadays is:(I guess
unittest
got tired of everyone flocking topytest
because of their helpful fixtures)像您一样调用
__enter__
和__exit__
的问题并不是您已经这样做了:它们可以在with
语句之外调用。问题在于,如果发生异常,您的代码无法正确调用对象的__exit__
方法。因此,实现此目的的方法是使用一个装饰器将对原始方法的调用包装在
with
语句中。一个简短的元类可以将装饰器透明地应用于类中名为 test* 的所有方法 -(我还包括“GetSlot”的模拟实现以及示例中的方法和函数,以便我自己可以测试我建议的装饰器和元类这个答案)
The problem with calling
__enter__
and__exit__
as you did, is not that you have done so: they can be called outside of awith
statement. The problem is that your code has no provision to call the object's__exit__
method properly if an exception occurs.So, the way to do it is to have a decorator that will wrap the call to your original method in a
with
statement. A short metaclass can apply the decorator transparently to all methods named test* in the class -(I also included mock implementations of "GetSlot" and the methods and functions in your example so that I myself could test the decorator and metaclass I am suggesting on this answer)
我认为您应该将上下文管理器的测试与 Slot 类的测试分开。您甚至可以使用模拟对象来模拟插槽的初始化/终结接口来测试上下文管理器对象,然后单独测试您的插槽对象。
这使得代码更简单,防止关注点混合,并允许您重用上下文管理器,而无需在许多地方对其进行编码。
I'd argue you should separate your test of the context manager from your test of the Slot class. You could even use a mock object simulating the initialize/finalize interface of slot to test the context manager object, and then test your slot object separately.
This makes code simpler, prevents concern mixing and allows you to reuse the context manager without having to code it in many places.