Как обрабатывать несколько утверждений в рамках одного элемента управления Python?

Это проблема, возникшая при выполнении одного теста с несколькими независимыми режимами отказа из-за наличия нескольких выходных потоков. Я также хотел показать результаты утверждения данных обо всех этих режимах, независимо от того, что произошло в первую очередь. У Python unittest нет такой функции за пределами использования Suite для представления одного теста, что было неприемлемо, поскольку мой единственный тест всегда нужно запускать как единое целое; он просто не фиксирует природу вещи.

Практическим примером является тестирование объекта, который также генерирует журнал. Вы хотите утверждать вывод его методов, но вы также хотите утверждать вывод журнала. Для двух выходов требуются разные тесты, которые могут быть четко выражены, поскольку два из выражений запаса утверждают, но вы также не хотите, чтобы один из них скрывал возможный сбой другого в тесте. Поэтому вам действительно нужно протестировать оба одновременно.

Я собрал этот полезный маленький виджет, чтобы решить мою проблему.

def logFailures(fnList):
    failurelog = []
    for fn in fnList:
        try:
            fn()
        except AssertionError as e:
            failurelog.append("\nFailure %d: %s" % (len(failurelog)+1,str(e)))

    if len(failurelog) != 0:
        raise AssertionError(
            "%d failures within test.\n %s" % (len(failurelog),"\n".join(failurelog))
        )

Используется так:

def test__myTest():
    # do some work here
    logFailures([
        lambda: assert_(False,"This test failed."),
        lambda: assert_(False,"This test also failed."),
    ])

В результате logFailures() вызовет исключение, содержащее журнал всех утверждений, поднятых в методах в списке.

Вопрос: В то время как это выполняет задание, мне остается задаться вопросом, есть ли лучший способ справиться с этим, кроме необходимости идти на длину создания вложенных наборов тестов и т.д.

Ответ 1

Я не согласен с доминирующим мнением о том, что нужно написать тестовый метод для каждого утверждения. Бывают ситуации, когда вы хотите проверить несколько вещей в одном методе тестирования. Вот мой ответ, как это сделать:

# Works with unittest in Python 2.7
class ExpectingTestCase(unittest.TestCase):
    def run(self, result=None):
        self._result = result
        self._num_expectations = 0
        super(ExpectingTestCase, self).run(result)

    def _fail(self, failure):
        try:
            raise failure
        except failure.__class__:
            self._result.addFailure(self, sys.exc_info())

    def expect_true(self, a, msg):
        if not a:
            self._fail(self.failureException(msg))
        self._num_expectations += 1

    def expect_equal(self, a, b, msg=''):
        if a != b:
            msg = '({}) Expected {} to equal {}. '.format(self._num_expectations, a, b) + msg
            self._fail(self.failureException(msg))
        self._num_expectations += 1

И вот некоторые ситуации, когда я считаю это полезным и не рискованным:

1) Если вы хотите проверить код для разных наборов данных. Здесь у нас есть функция add(), и я хочу протестировать ее несколькими примерами ввода. Чтобы написать 3 метода тестирования для 3 наборов данных, нужно повторить себя, что плохо. Особенно, если вызов был более сложным.

class MyTest(ExpectingTestCase):
    def test_multiple_inputs(self):
        for a, b, expect in ([1,1,2], [0,0,0], [2,2,4]):
            self.expect_equal(expect, add(a,b), 'inputs: {} {}'.format(a,b))

2) Если вы хотите проверить несколько выходов функции. Я хочу проверить каждый вывод, но я не хочу, чтобы первый сбой маскировал другие два.

class MyTest(ExpectingTestCase):
    def test_things_with_no_side_effects(self):
        a, b, c = myfunc()
        self.expect_equal('first value', a)
        self.expect_equal('second value', b)
        self.expect_equal('third value', c)

3) Тестирование вещей с большими затратами на установку. Тесты должны выполняться быстро или люди перестают их использовать. Для некоторых тестов требуется db или сетевое соединение, которое занимает секунду, что действительно замедлит ваш тест. Если вы тестируете соединение db самостоятельно, вам, вероятно, потребуется принять удар скорости. Но если вы тестируете что-то несвязанное, мы хотим сделать медленную настройку один раз для целого набора проверок.

Ответ 2

При использовании подтеста выполнение не остановится после первого сбоя https://docs.python.org/3/library/unittest.html#subtests

Вот пример с двумя ошибочными утверждениями:

class TestMultipleAsserts(unittest.TestCase):

    def test_multipleasserts(self):
        with self.subTest():
            self.assertEqual(1, 0)
        with self.subTest():
            self.assertEqual(2, 0)

Вывод будет:

======================================================================
FAIL: test_multipleasserts (__main__.TestMultipleAsserts) (<subtest>)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test.py", line 9, in test_multipleasserts
    self.assertEqual(1, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_multipleasserts (__main__.TestMultipleAsserts) (<subtest>)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test.py", line 11, in test_multipleasserts
    self.assertEqual(2, 0)
AssertionError: 2 != 0

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=2)

Вы можете легко обернуть подтест следующим образом

class MyTestCase(unittest.TestCase):
    def expectEqual(self, first, second, msg=None):
        with self.subTest():
            self.assertEqual(first, second, msg)

class TestMA(MyTestCase):
    def test_ma(self):
        self.expectEqual(3, 0)
        self.expectEqual(4, 0)

Ответ 3

Мне кажется, что это слишком сложно. Или:

  • Используйте два утверждения в одном тестовом примере. Если первое утверждение терпит неудачу, это правда, вы не узнаете, прошло ли второе утверждение или нет. Но вы все равно исправите код, поэтому исправьте его, а затем вы узнаете, прошло ли второе утверждение.

  • Напишите два теста, чтобы проверить каждое условие. Если вы испытываете дублирование кода в тестах, поместите основную часть кода в вспомогательный метод, который вы вызываете из тестов.