Python unittest - assertRaises的反面?

我想编写一个测试,以确定在给定的情况下不会引发异常。

测试异常是否引发…

sInvalidPath=AlwaysSuppliesAnInvalidPath()
self.assertRaises(PathIsNotAValidOne, MyObject, sInvalidPath)

... 但是你怎么做相反

像这样的东西是我所追求的…

sValidPath=AlwaysSuppliesAValidPath()
self.assertNotRaises(PathIsNotAValidOne, MyObject, sValidPath)
171962 次浏览

只需要调用函数。如果它引发异常,单元测试框架会将其标记为错误。你可能想要添加一个评论,例如:

sValidPath=AlwaysSuppliesAValidPath()
# Check PathIsNotAValidOne not thrown
MyObject(sValidPath)

编辑以补充评论中的澄清:

  • 单元测试可以有3种结果:通过、失败、错误。(如果算上XPass/XFail/Skip,实际上更多)
  • 如果您正在测试一个特定的异常没有被抛出,而它被抛出,那么理论上它应该是一个Fail。但是上面的代码使它成为一个错误,这在理论上是“错误”的。
  • 作为一个实际问题,使用Error,测试运行器可能会打印堆栈跟踪,这可能对调试失败有用。对于Fail,您可能看不到堆栈跟踪。
  • 作为一个实际的问题,使用Fail你可以将测试标记为“预期失败”。对于Error,您可能无法做到这一点,尽管您可以将测试标记为“skip”。
  • 作为一个实际的问题,让测试用例报告一个错误需要额外的代码。
  • 是否“;Error"和“;Failure"问题取决于您的流程。我的团队使用单元测试的方式,它们都必须通过。(敏捷编程,使用持续集成机器运行所有单元测试)。对我的团队来说真正重要的是“是否所有的单元测试都通过了?”(即“Jenkins是绿色的吗?”)。所以对我的团队来说,“失败”和“失败”之间并没有实际的区别。和“;Error"。
  • 由于上面提到的优点(代码更少,可以看到堆栈跟踪),以及我的团队对失败/错误的处理是相同的,所以我使用了这种方法。
  • 如果您以不同的方式使用单元测试,您可能会有不同的需求,特别是如果您的过程处理“失败”;和“;error"不同,或者如果您希望能够将测试标记为“预期失败”。
  • 如果您希望该测试报告错误,请使用DGH的答案。
def run_test(self):
try:
myFunc()
except ExceptionType:
self.fail("myFunc() raised ExceptionType unexpectedly!")

嗨,我想写一个测试来确定在给定的情况下不会引发异常。

这是默认的假设——不引发异常。

如果你什么都没说,那在每个测试中都是假设的。

你不需要为它写任何断言。

我是最初的海报,我接受了DGH的上述答案,而没有首先在代码中使用它。

一旦我使用了它,我意识到它需要做一些调整才能真正做到我需要它做的事情(公平地说,他/她确实说了“或类似的东西!”)。

为了其他人的利益,我认为有必要在这里发布这个调整:

    try:
a = Application("abcdef", "")
except pySourceAidExceptions.PathIsNotAValidOne:
pass
except:
self.assertTrue(False)

我在这里试图做的是确保如果尝试实例化一个应用程序对象,使用第二个空间参数pySourceAidExceptions。PathIsNotAValidOne将被引发。

我相信使用上面的代码(主要基于DGH的回答)可以做到这一点。

如果你将一个Exception类传递给assertRaises(),就会提供一个上下文管理器。这可以提高测试的可读性:

# raise exception if Application created with bad data
with self.assertRaises(pySourceAidExceptions.PathIsNotAValidOne):
application = Application("abcdef", "")

这允许您测试代码中的错误用例。

在这种情况下,您正在测试在向应用程序构造函数传递无效参数时引发PathIsNotAValidOne

我发现monkey-patch unittest很有用,如下所示:

def assertMayRaise(self, exception, expr):
if exception is None:
try:
expr()
except:
info = sys.exc_info()
self.fail('%s raised' % repr(info[0]))
else:
self.assertRaises(exception, expr)


unittest.TestCase.assertMayRaise = assertMayRaise

这在测试异常是否存在时阐明了意图:

self.assertMayRaise(None, does_not_raise)

这也简化了循环测试,我经常这样做:

# ValueError is raised only for op(x,x), op(y,y) and op(z,z).
for i,(a,b) in enumerate(itertools.product([x,y,z], [x,y,z])):
self.assertMayRaise(None if i%4 else ValueError, lambda: op(a, b))

可以通过重用unittest模块中大约90%的assertRaises原始实现来定义assertNotRaises。使用这种方法,你最终得到了一个assertNotRaises方法,除了它的反向失败条件外,它的行为与assertRaises完全相同。

TLDR和现场演示

事实证明,向unittest.TestCase添加assertNotRaises方法非常容易(我写这个答案的时间大约是写代码的4倍)。这是assertNotRaises方法的实时演示。只是assertRaises,你可以将可调用对象和参数传递给assertNotRaises,或者你可以在with语句中使用它。现场演示包括一个测试用例,演示assertNotRaises按预期工作。

细节

unittest中实现assertRaises是相当复杂的,但是通过一些聪明的子类化,你可以覆盖和反转它的失败条件。

assertRaises是一个简短的方法,基本上只创建一个unittest.case._AssertRaisesContext类的实例并返回它(请参阅unittest.case模块中的定义)。你可以通过继承_AssertRaisesContext的子类并覆盖它的__exit__方法来定义自己的_AssertNotRaisesContext类:

import traceback
from unittest.case import _AssertRaisesContext


class _AssertNotRaisesContext(_AssertRaisesContext):
def __exit__(self, exc_type, exc_value, tb):
if exc_type is not None:
self.exception = exc_value.with_traceback(None)


try:
exc_name = self.expected.__name__
except AttributeError:
exc_name = str(self.expected)


if self.obj_name:
self._raiseFailure("{} raised by {}".format(exc_name,
self.obj_name))
else:
self._raiseFailure("{} raised".format(exc_name))


else:
traceback.clear_frames(tb)


return True

通常你通过继承TestCase来定义测试用例类。如果你从子类MyTestCase继承:

class MyTestCase(unittest.TestCase):
def assertNotRaises(self, expected_exception, *args, **kwargs):
context = _AssertNotRaisesContext(expected_exception, self)
try:
return context.handle('assertNotRaises', args, kwargs)
finally:
context = None

你所有的测试用例现在都有assertNotRaises方法可用。

你可以试试。 试一试: 自我。assertRaises(没有,函数,__arg1、最长) 除了: 通过 如果你不把代码放在try块中,它会通过异常“AssertionError: None not raised”,测试用例将失败。如果将测试用例放在try块中,则测试用例将通过,这是预期的行为

def _assertNotRaises(self, exception, obj, attr):
try:
result = getattr(obj, attr)
if hasattr(result, '__call__'):
result()
except Exception as e:
if isinstance(e, exception):
raise AssertionError('{}.{} raises {}.'.format(obj, attr, exception))

如果需要接受参数,可以修改。

叫喜欢

self._assertNotRaises(IndexError, array, 'sort')

确保对象被初始化而没有任何错误的一种直接方法是测试对象的类型实例。

这里有一个例子:

p = SomeClass(param1=_param1_value)
self.assertTrue(isinstance(p, SomeClass))