不懂assertRaises with generators + unittest

Do not understand assertRaises with generators + unittest

我不明白哪里出了问题。这是 2 个简单的范围生成器。两者都会在输入大于 MAX 时引发 ValueError。第一个是生成器理解,第二个使用 yield。

MSG = 'Wrong number'
MAX = 20

def test_compr(n, m=MAX):
    if n > m:
        raise ValueError('{} {} in {}'.format(MSG, n, test_compr.__name__))
    return (i for i in range(n))

def test_yield(n, m=MAX):
    if n > m:
        raise ValueError('{} {} in {}'.format(MSG, n, test_yield.__name__))
    i = 0
    while i < n:
        yield i
        i += 1

def main():
    n = 30

    try:
        print(list(test_compr(n)))
    except ValueError as e:
        print(e)

    try:
        print(list(test_yield(n)))
    except ValueError as e:
        print(e)


if __name__ == '__main__':
        main()
python3 --version  && python t.py
Python 3.7.5
Wrong number 30 in test_compr
Wrong number 30 in test_yield

这按预期工作。 现在进行一些单元测试以确保确实引发了 ValueError。

import unittest
from t import test_compr, test_yield

N_BIG = 30
N_OK = 19


# test generator comprehension
class TestCompr(unittest.TestCase):
    def test_ok(self):
        tst = N_OK
        g = test_compr(tst)
        for num in range(tst):
            self.assertEqual(num, next(g))

    def test_exception(self):
        TST = N_BIG
        with self.assertRaises(ValueError) as c:
            test_compr(N_BIG)

# test yield
class TestYield(unittest.TestCase):
    def test_ok(self):
        tst = N_OK
        g = test_yield(tst)
        for num in range(tst):
            self.assertEqual(num, next(g))

    def test_exception(self):
        tst=N_BIG
        with self.assertRaises(ValueError) as c:
            test_yield(tst)

if __name__ == '__main__':
    unittest.main()
> python3 tests.py
Python 3.7.5
..F.
======================================================================
FAIL: test_exception (__main__.TestYield)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 31, in test_exception
    test_yield(tst)
AssertionError: ValueError not raised

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=1)

使用生成器理解进行测试会引发错误,但使用生成器进行 yield 测试不会。一旦删除 yield,我就会看到 unittest 在 assertRaises

上产生了正确的结果

已尝试安装最新的 python。同样的问题。

python3.8 --version && python3.8 tests.py
Python 3.8.0
..F.
======================================================================
FAIL: test_exception (__main__.TestYield)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 31, in test_exception
    test_yield(tst)
AssertionError: ValueError not raised

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=1)

不确定哪里出了问题以及如何解决这个问题。我从 python3.5 上稍微复杂的代码开始,结果几乎相同。在上面制作样板以在简单的代码上重现问题。

这种看似不一致的行为的原因如下:

  • test_yield 是生成函数
  • test_compr 是一个函数,returns 一个生成器

这意味着,当您调用 test_compr 时,函数体将立即被执行,结果 returns 一个生成器(或抛出)。

相比之下,当您调用 test_yield 时,您会收到一个生成器对象。生成器的执行不会在创建时开始,而是在第一次调用 next 函数时开始。因此,您的测试函数应如下所示:

def test_exception(self):
    with self.assertRaises(ValueError) as c:
        g = test_yield(N_BIG)
        next(g)