Как мне обработать несколько утверждений в одном тесте Python?
Эта проблема возникла при выполнении одного теста с несколькими независимыми режимами отказа из-за наличия нескольких выходных потоков. Я также хотел показать результаты утверждения данных во всех этих режимах, независимо от того, какой из них вышел из строя первым. У юнит-теста Python такой функции нет, кроме использования 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() сгенерирует исключение, содержащее журнал всех утверждений, которые были вызваны в методах в списке.
Вопрос: в то время как это делает работу, мне остается задаться вопросом, есть ли лучший способ справиться с этим, кроме того, чтобы пойти на длину создания вложенных наборов тестов и так далее?
3 ответа
При использовании подтеста выполнение не будет остановлено после первого сбоя https://docs.python.org/3/library/unittest.html
Вот пример с двумя ошибочными утверждениями:
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)
Я не согласен с доминирующим мнением о том, что для каждого утверждения необходимо написать метод проверки. Есть ситуации, когда вы хотите проверить несколько вещей в одном методе тестирования. Вот мой ответ, как это сделать:
# 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) Тестирование вещей с большими затратами на настройку. Тесты должны выполняться быстро, иначе люди перестанут их использовать. Некоторые тесты требуют подключения к БД или сети, которое занимает секунду, что действительно замедляет ваш тест. Если вы тестируете само соединение с БД, то вам, вероятно, нужно принять удар по скорости. Но если вы тестируете что-то не связанное, мы хотим выполнить медленную настройку один раз для всего набора проверок.
Это похоже на чрезмерную инженерию для меня. Или:
Используйте два утверждения в одном тестовом примере. Если первое утверждение не удалось, это правда, вы не будете знать, прошло второе утверждение или нет. Но вы все равно исправите код, поэтому исправьте его, и тогда вы узнаете, прошло ли второе утверждение.
Напишите два теста, один для проверки каждого условия. Если вы боитесь дублирования кода в тестах, поместите основную часть кода в вспомогательный метод, который вы вызываете из тестов.