关于单元测试:python unittest-与assertRaises引发相反?

Python unittest - opposite of assertRaises?

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

测试是否引发异常很简单…

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

…但是你怎么能做相反的事情呢?

像这样的东西,我想要的……

1
2
sValidPath=AlwaysSuppliesAValidPath()
self.assertNotRaises(PathIsNotAValidOne, MyObject, sValidPath)


1
2
3
4
5
def run_test(self):
    try:
        myFunc()
    except ExceptionType:
        self.fail("myFunc() raised ExceptionType unexpectedly!")


Hi - I want to write a test to establish that an Exception is not raised in a given circumstance.

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

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

你不必为此写任何声明。


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

1
2
3
sValidPath=AlwaysSuppliesAValidPath()
# Check PathIsNotAValidOne not thrown
MyObject(sValidPath)


我是原来的海报,我接受了上面的回答,没有在代码中使用它。

一旦我使用了它,我意识到它需要一点小调整才能真正做到我需要它做的事情(公平地说,他/她确实说了"或类似的事情"!).

我认为为了其他人的利益,在这里张贴调整是值得的:

1
2
3
4
5
6
    try:
        a = Application("abcdef","")
    except pySourceAidExceptions.PathIsNotAValidOne:
        pass
    except:
        self.assertTrue(False)

我在这里要做的是确保,如果试图用第二个空格参数实例化应用程序对象,那么PysourceAidexceptions.PathInotavalidone将被引发。

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


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

TLDR和现场演示

事实证明,将assertNotRaises方法添加到unittest.TestCase中非常容易(编写这个答案的时间是编写代码的4倍左右)。这是一个实际的assertNotRaises方法演示。就像assertRaises一样,既可以将callable和args传递给assertNotRaises,也可以在with语句中使用。实况演示包括一个测试案例,演示了assertNotRaises按预期工作。

细节

unittestassertRaises的实现相当复杂,但是有了一点巧妙的子类化,您可以覆盖和逆转它的失败条件。

assertRaises是一个简短的方法,它基本上只是创建unittest.case._AssertRaisesContext类的一个实例并返回它(参见其在unittest.case模块中的定义)。您可以通过子类化_AssertRaisesContext并重写其__exit__方法来定义自己的_AssertNotRaisesContext类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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子类:

1
2
3
4
5
6
7
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方法。


我发现它对猴子补丁unittest很有用,如下所示:

1
2
3
4
5
6
7
8
9
10
11
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

这说明了在测试无异常情况时的意图:

1
self.assertMayRaise(None, does_not_raise)

这也简化了循环中的测试,我经常发现自己在做:

1
2
3
# 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))


如果将异常类传递给assertRaises(),将提供上下文管理器。这可以提高测试的可读性:

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

这允许您在代码中测试错误情况。

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


1
2
3
4
5
6
7
8
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))

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

像呼叫

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

你可以这样试试。尝试:self.assertraises(无,函数,arg1,arg2)除:通过如果不将代码放入try块中,它将通过异常"断言错误:无未引发",测试用例将失败。如果将测试用例放入预期行为的try块中,则该测试用例将通过。