【问题标题】:Using a context manager with Python assertRaises在 Python assertRaises 中使用上下文管理器
【发布时间】:2011-11-21 17:10:20
【问题描述】:

unittest 的 Python 文档暗示 assertRaises() 方法可以用作上下文管理器。下面显示的代码给出了 Python 文档中 unittest 的简单示例。 testsample() 方法中的 assertRaises() 调用工作正常。

现在我想在引发异常时访问它,但如果我将其注释掉,而是取消注释我尝试使用上下文管理器的下一个块,我会在尝试执行时得到一个 AttributeError: __exit__编码。 Python 2.7.2 和 3.2.2 都会发生这种情况。我可以在 try...except 块中捕获异常并以这种方式访问​​它,但 unittest 的文档似乎暗示上下文管理器也会这样做。

还有什么我做错了吗?

class TestSequenceFunctions(unittest.TestCase):

    def setUp(self):
        self.seq = [x for x in range(10)]

    def testshuffle(self):
        # make sure the shuffled sequence does not lose any elements
        random.shuffle(self.seq)
        self.seq.sort()
        self.assertEqual(self.seq, [x for x in range(10)])

    def testchoice(self):
        element = random.choice(self.seq)
        self.assert_(element in self.seq)

    def testsample(self):
        self.assertRaises(ValueError, random.sample, self.seq, 20)

        # with self.assertRaises(ValueError, random.sample, self.seq, 20):
        #     print("Inside cm")

        for element in random.sample(self.seq, 5):
            self.assert_(element in self.seq)

if __name__ == '__main__':
    unittest.main()

【问题讨论】:

    标签: python unit-testing


    【解决方案1】:

    似乎还没有人建议:

    import unittest
    # For python < 2.7, do import unittest2 as unittest
    
    class Class(object):
        def should_raise(self):
            raise ValueError('expected arg')
    
    class test_Class(unittest.TestCase):
        def test_something(self):
            DUT = Class()
            with self.assertRaises(ValueError) as exception_context_manager:
                DUT.should_raise()
            exception = exception_context_manager.exception
    
            self.assertEqual(exception.args, ('expected arg', ))
    

    我通常使用 e_cm 作为 exception_context_manager 的缩写。

    【讨论】:

    • 对我来说没问题。请记住,您必须在 unittest.Testcase 实例的方法中执行此操作。如果您使用的是旧版本的 python,您还需要安装和使用 unittest2。我将扩展我的答案以显示完整的示例。
    • 你为我节省了大量的搜索。谢谢。
    • 这实际上是比公认的答案更好的选择,恕我直言。起初我认为它不适用于我的 python 2.7,直到我意识到你不能像我尝试的那样从 which 块内部访问 context_manager.exception。但是,此答案中的代码是正确的,并且可以完美运行。谢谢你。
    • @NeilenMarais - 你能帮我解决一个相关的问题吗? stackoverflow.com/questions/39909935/…
    • @BoratSagdiyev 看起来你已经有了一个可以接受的答案,有什么要我补充的吗?
    【解决方案2】:

    source code for unittest 没有显示 assertRaises 的异常挂钩:

    class _AssertRaisesContext(object):
        """A context manager used to implement TestCase.assertRaises* methods."""
    
        def __init__(self, expected, test_case, expected_regexp=None):
            self.expected = expected
            self.failureException = test_case.failureException
            self.expected_regexp = expected_regexp
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_value, tb):
            if exc_type is None:
                try:
                    exc_name = self.expected.__name__
                except AttributeError:
                    exc_name = str(self.expected)
                raise self.failureException(
                    "{0} not raised".format(exc_name))
            if not issubclass(exc_type, self.expected):
                # let unexpected exceptions pass through
                return False
            self.exception = exc_value # store for later retrieval
            if self.expected_regexp is None:
                return True
    
            expected_regexp = self.expected_regexp
            if isinstance(expected_regexp, basestring):
                expected_regexp = re.compile(expected_regexp)
            if not expected_regexp.search(str(exc_value)):
                raise self.failureException('"%s" does not match "%s"' %
                         (expected_regexp.pattern, str(exc_value)))
            return True
    

    因此,正如您所怀疑的,如果您想拦截异常同时仍保持 assertRaises 测试,那么形成自己的 try/except 块是一种方法:

    def testsample(self):
        with self.assertRaises(ValueError):
             try:
                 random.sample(self.seq, 20)
             except ValueError as e:
                 # do some action with e
                 self.assertEqual(e.args,
                                  ('sample larger than population',))
                 # now let the context manager do its work
                 raise                    
    

    【讨论】:

    • 谢谢,这可行,但似乎这与文档描述它的方式相反,除非我读错了。在 R Hettinger 的更正代码中,您似乎并不需要上下文管理器。您从上下文管理器中获得了什么?您已经捕获了要测试的异常。如果没有发生异常,您总是可以在与异常子句相关的其他子句中发出信号。
    • @PaulJoireman:是的,如果您愿意,可以不使用 assertRaises 来执行此操作。上下文管理器只是让简单案例(检查是否引发了特定异常)更易于阅读。
    • 你能帮我解决一个相关的问题吗? stackoverflow.com/questions/39909935/…
    【解决方案3】:

    鉴于六年前有人问过这个问题,我想这是现​​在可行但当时行不通的东西。 docs 状态这个出现在 2.7 但不是哪个微版本。

    import unittest
    
    class TestIntParser(unittest.TestCase):
    
        def test_failure(self):
            failure_message = 'invalid literal for int() with base 10'
            with self.assertRaises(ValueError) as cm:
                int('forty two')
            self.assertIn(failure_message, cm.exception.message)
    
    if __name__ == '__main__':
        unittest.main()
    

    【讨论】:

    【解决方案4】:

    根据文档:

    如果调用时省略了 callableObj 或 None,将返回一个上下文对象

    所以代码应该是:

    with self.assertRaises(ValueError):
        random.sample(self.seq, 20)
    

    【讨论】:

    • 如果我正确理解了 OP 的问题,看起来他想拦截异常并使用它做额外的工作(可能断言底层消息或类似的东西),所以我不认为这个答案有帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-25
    • 2013-03-03
    • 2022-12-17
    相关资源
    最近更新 更多