Не понимаю assertRaises с генераторами + unittest

Я не понимаю, что случилось. Вот 2 простых генератора диапазона. Оба вызывают ValueError при вводе больше, чем MAX. Первый — это понимание генератора, а второй использует доходность.

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, я вижу, что 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 с почти такими же результатами. Сделал шаблон выше, чтобы воспроизвести проблему на простом коде.


person Eugene Podoba    schedule 26.03.2020    source источник


Ответы (1)


Причина такого, казалось бы, непоследовательного поведения заключается в следующем:

  • test_yield — функция-генератор
  • test_compr — это функция, которая возвращает генератор

Это означает, что при вызове test_compr тело функции будет немедленно выполнено, а в качестве результата будет возвращен генератор (или броски).

Напротив, когда вы вызываете test_yield, вы получаете объект-генератор. Выполнение генератора начинается не при его создании, а при первом вызове функции next. Таким образом, ваша тестовая функция должна выглядеть следующим образом:

def test_exception(self):
    with self.assertRaises(ValueError) as c:
        g = test_yield(N_BIG)
        next(g)
person Dirk Herrmann    schedule 30.09.2020