Python unittest - противоположность assertRaises?


374

Я хочу написать тест, чтобы установить, что Исключение не возникает в определенных обстоятельствах.

Это просто для тестирования , если исключение будет поднято ...

sInvalidPath=AlwaysSuppliesAnInvalidPath()
self.assertRaises(PathIsNotAValidOne, MyObject, sInvalidPath) 

... но как вы можете сделать обратное .

Что-то вроде этого я, что я после ...

sValidPath=AlwaysSuppliesAValidPath()
self.assertNotRaises(PathIsNotAValidOne, MyObject, sValidPath) 

6
Вы всегда можете просто сделать все, что должно работать в тесте. Если возникает ошибка, она будет отображаться (считается ошибкой, а не ошибкой). Конечно, это предполагает, что это не вызывает никакой ошибки, а не только определенного типа ошибки. Кроме этого, я думаю, вам придется написать свой собственный.
Томас К


Оказывается, что на самом деле вы можете реализовать assertNotRaisesметод, который разделяет 90% своего кода / поведения assertRaisesпримерно через 30 строк кода. Смотрите мой ответ ниже для деталей.
тел

Я хочу, чтобы я мог сравнить две функции, hypothesisчтобы убедиться, что они выдают одинаковый вывод для всех видов ввода, игнорируя при этом случаи, когда оригинал вызывает исключение. assume(func(a))не работает, потому что выходные данные могут быть массивом с неоднозначным значением истинности. Поэтому я просто хочу вызвать функцию и получить, Trueесли она не дает сбоя. assume(func(a) is not None)работы я думаю
эндолиты

Ответы:


394
def run_test(self):
    try:
        myFunc()
    except ExceptionType:
        self.fail("myFunc() raised ExceptionType unexpectedly!")

32
@hiwaylon - Нет, это правильное решение на самом деле. Решение, предложенное пользователем9876, является концептуально ошибочным: если вы проверяете отсутствие поднятия слова ValueError, но ValueErrorвместо этого оно поднимается, ваш тест должен завершиться с условием сбоя, а не с ошибкой. С другой стороны, если при запуске того же кода вы вызовете a KeyError, это будет ошибкой, а не ошибкой. В Python - в отличие от некоторых других языков - исключения обычно используются для потока управления, поэтому у нас except <ExceptionName>действительно есть синтаксис. В связи с этим, решение user9876 просто неверно.
Mac

@mac - это тоже правильное решение? stackoverflow.com/a/4711722/6648326
MasterJoe2

Это имеет неприятный эффект: показывается <100% охват (за исключением случаев, когда это не произойдет) для тестов.
Шей

3
@Shay, IMO, вы всегда должны исключать сами тестовые файлы из отчетов о покрытии (поскольку они почти всегда работают на 100% по определению, вы бы искусственно надували отчеты)
Оригинальный соус для барбекю

@ original-bbq-sauce, разве это не оставит меня открытым для непреднамеренно пропущенных тестов? Например, опечатка в имени теста (ttst_function), неправильная конфигурация запуска в pycharm и т. Д.?
Шей

67

Привет. Я хочу написать тест, чтобы установить, что Исключение не возникает при определенных обстоятельствах.

Это предположение по умолчанию - исключения не возникают.

Если вы не говорите ничего другого, это предполагается в каждом тесте.

Вам не нужно писать какое-либо утверждение для этого.


7
@IndradhanushGupta Хорошо принятый ответ делает тест более питонным, чем этот. Явное лучше, чем неявное.
0xc0de

17
Ни один другой комментатор не указал, почему этот ответ неправильный, хотя по той же причине неправильный ответ user9876: сбои и ошибки - разные вещи в тестовом коде. Если ваша функция была бросить исключение во время теста , который не утверждает, тест рамка будет лечить , что как ошибку , а не отказ от не утверждают.
coredumperror

@CoreDumpError Я понимаю разницу между ошибкой и ошибкой, но разве это не заставит вас окружать каждый тест конструкцией «попытка / исключение»? Или вы рекомендовали бы делать это только для тестов, которые явно вызывают исключение при некотором условии (что в основном означает, что исключение ожидается ).
federicojasson

4
@federicojasson Вы хорошо ответили на свой вопрос во втором предложении. Ошибки против сбоев в тестах можно кратко описать как «непредвиденные сбои» против «непреднамеренного поведения», соответственно. Вы хотите, чтобы ваши тесты отображали ошибку при сбое вашей функции, но не когда исключение, которое, как вы знаете, будет генерироваться при определенных входных данных, будет выдано при наличии различных входных данных.
coredumperror

52

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

sValidPath=AlwaysSuppliesAValidPath()
# Check PathIsNotAValidOne not thrown
MyObject(sValidPath)

35
Ошибки и ошибки концептуально различны. Кроме того, поскольку в python исключения обычно используются для управления потоком, это очень затруднит понимание с первого взгляда (= без изучения тестового кода), если вы нарушили свою логику или код ...
mac

1
Либо ваш тест проходит, либо нет. Если это не пройдет, вам придется это исправить. Сообщается ли это как «сбой» или «ошибка», в основном не имеет значения. Есть одно отличие: с моим ответом вы увидите трассировку стека, чтобы вы могли увидеть, куда был брошен PathIsNotAValidOne; с принятым ответом у вас не будет этой информации, поэтому отладка будет сложнее. (Предполагается, что Py2; не уверен, что Py3 лучше в этом).
user9876

19
@ user9876 - Нет. Условия выхода из теста - 3 (проход / обход / ошибка), а не 2, как вы, кажется, ошибочно полагаете. Разница между ошибками и сбоями существенна, и рассматривать их, как если бы они были одинаковыми, просто плохое программирование. Если вы мне не верите, просто посмотрите, как работают тестировщики и какие деревья решений они применяют для сбоев и ошибок. Хорошая отправная точка для питона декоратор в pytest. xfail
Mac

4
Я думаю, это зависит от того, как вы используете юнит-тесты. То, как моя команда использует модульные тесты, они должны все пройти. (Гибкое программирование, с машиной непрерывной интеграции, которая запускает все модульные тесты). Я знаю, что тестовый пример может сообщить «пройти», «провал» или «ошибка». Но на высоком уровне, что на самом деле имеет значение для моей команды: «все ли юнит-тесты пройдены?» (то есть "Дженкинс зеленый?"). Так что для моей команды нет практической разницы между «неудачей» и «ошибкой». У вас могут быть другие требования, если вы используете свои юнит-тесты по-другому.
user9876

1
@ user9876 разница между «fail» и «error» - это разница между «my assert fail» и «мой тест даже не доходит до assert». Для меня это полезное различие при проведении тестов, но, как вы говорите, не для всех.
CS

14

Я оригинальный постер, и я принял приведенный выше ответ DGH, но не использовал его в коде.

Как только я использовал, я понял, что для того, чтобы сделать то, что мне нужно, нужно немного подправить (чтобы быть справедливым по отношению к DGH, он / она сказал «или что-то подобное»!).

Я думал, что здесь стоит опубликовать твик для блага других:

    try:
        a = Application("abcdef", "")
    except pySourceAidExceptions.PathIsNotAValidOne:
        pass
    except:
        self.assertTrue(False)

То, что я пытался сделать здесь, состояло в том, чтобы гарантировать, что, если была сделана попытка создать экземпляр объекта Application со вторым аргументом пробелов, pySourceAidExceptions.PathIsNotAValidOne будет вызван.

Я полагаю, что использование приведенного выше кода (в значительной степени основанного на ответе DGH) сделает это.


2
Поскольку вы уточняете свой вопрос и не отвечаете на него, вы должны были отредактировать его (не ответив на него). Пожалуйста, смотрите мой ответ ниже.
Hiwaylon

13
Кажется, что это совершенно противоположно исходной проблеме. self.assertRaises(PathIsNotAValidOne, MyObject, sInvalidPath)должен сделать работу в этом случае.
Энтони Хэтчкинс

8

Вы можете определить assertNotRaises, повторно используя около 90% первоначальной реализации assertRaisesв unittestмодуле. При таком подходе вы в конечном итоге получаете assertNotRaisesметод, который, помимо своего условия обратного сбоя, ведет себя идентично assertRaises.

TLDR и живая демонстрация

Оказалось, что добавить assertNotRaisesметод к удивительно легко unittest.TestCase(у меня ушло примерно в 4 раза больше времени, чтобы написать этот ответ, чем при написании кода). Вот живая демонстрация assertNotRaisesметода в действии . Точно так жеassertRaises , вы можете либо передать вызываемый объект и аргументы assertNotRaises, либо использовать его в withвыражении. Демонстрационная версия включает тестовые примеры, которые демонстрируют, что assertNotRaisesработает как задумано.

подробности

Реализация assertRaisesin unittestдовольно сложна, но с небольшим количеством умных подклассов вы можете переопределить и отменить условие сбоя.

assertRaisesэто короткий метод, который в основном просто создает экземпляр unittest.case._AssertRaisesContextкласса и возвращает его (см. его определение в unittest.caseмодуле). Вы можете определить свой собственный _AssertNotRaisesContextкласс, создав подкласс _AssertRaisesContextи переопределив его __exit__метод:

import traceback
from unittest.case import _AssertRaisesContext

class _AssertNotRaisesContext(_AssertRaisesContext):
    def __exit__(self, exc_type, exc_value, tb):
        if exc_type is not None:
            self.exception = exc_value.with_traceback(None)

            try:
                exc_name = self.expected.__name__
            except AttributeError:
                exc_name = str(self.expected)

            if self.obj_name:
                self._raiseFailure("{} raised by {}".format(exc_name,
                    self.obj_name))
            else:
                self._raiseFailure("{} raised".format(exc_name))

        else:
            traceback.clear_frames(tb)

        return True

Обычно вы определяете классы тестовых наборов, наследуя их от TestCase. Если вы вместо этого наследуете от подкласса MyTestCase:

class MyTestCase(unittest.TestCase):
    def assertNotRaises(self, expected_exception, *args, **kwargs):
        context = _AssertNotRaisesContext(expected_exception, self)
        try:
            return context.handle('assertNotRaises', args, kwargs)
        finally:
            context = None

у всех ваших тестовых случаев теперь есть assertNotRaisesметод, доступный для них.


Где ваш tracebackв вашем elseзаявлении приходят?
18.10

1
@Nohs пропал без вести import. Это исправлено
тел

2
def _assertNotRaises(self, exception, obj, attr):                                                                                                                              
     try:                                                                                                                                                                       
         result = getattr(obj, attr)                                                                                                                                            
         if hasattr(result, '__call__'):                                                                                                                                        
             result()                                                                                                                                                           
     except Exception as e:                                                                                                                                                     
         if isinstance(e, exception):                                                                                                                                           
            raise AssertionError('{}.{} raises {}.'.format(obj, attr, exception)) 

может быть изменено, если вам нужно принять параметры.

называть как

self._assertNotRaises(IndexError, array, 'sort')

1

Я нашел это полезным для monkey-patch unittestследующим образом:

def assertMayRaise(self, exception, expr):
  if exception is None:
    try:
      expr()
    except:
      info = sys.exc_info()
      self.fail('%s raised' % repr(info[0]))
  else:
    self.assertRaises(exception, expr)

unittest.TestCase.assertMayRaise = assertMayRaise

Это проясняет намерение при тестировании на отсутствие исключения:

self.assertMayRaise(None, does_not_raise)

Это также упрощает тестирование в цикле, который я часто выполняю:

# ValueError is raised only for op(x,x), op(y,y) and op(z,z).
for i,(a,b) in enumerate(itertools.product([x,y,z], [x,y,z])):
  self.assertMayRaise(None if i%4 else ValueError, lambda: op(a, b))

Что такое обезьяна-патч?
ScottMcC

1
См. En.wikipedia.org/wiki/Monkey_patch . После добавления assertMayRaiseк unittest.TestSuiteвам может просто делать вид , что часть unittestбиблиотеки.
AndyJost

0

Если вы передаете класс Exception в assertRaises(), предоставляется менеджер контекста. Это может улучшить читаемость ваших тестов:

# raise exception if Application created with bad data
with self.assertRaises(pySourceAidExceptions.PathIsNotAValidOne):
    application = Application("abcdef", "")

Это позволяет вам проверять ошибки в вашем коде.

В этом случае вы проверяете, PathIsNotAValidOneкогда вы передаете неверные параметры конструктору приложения.


1
Нет, это произойдет только в том случае, если исключение не вызвано в блоке диспетчера контекста. Может быть легко протестировано с помощью «self.assertRaises (TypeError): поднять TypeError», которое проходит.
Мэтью Тревор

@ MatthewTrevor Хороший звонок. Насколько я помню, вместо того, чтобы тестировать код, выполняется правильно, т.е. не возникает, я предлагал тестировать ошибки. Я отредактировал ответ соответственно. Надеюсь, я смогу заработать +1, чтобы выйти из красных. :)
hiwaylon

Обратите внимание, это также Python 2.7 и более поздние версии
qneill

0

Вы можете попробовать так. try: self.assertRaises (None, function, arg1, arg2) кроме: pass, если вы не поместите код в блок try, это произойдет через исключение «AssertionError: None not воспитано», и тестовый случай будет пройден. Тестовый пример будет пройден если положить внутрь, попробуйте блок, который ожидается поведение.


0

Один прямой способ убедиться, что объект инициализируется без каких-либо ошибок, - это проверить экземпляр типа объекта.

Вот пример:

p = SomeClass(param1=_param1_value)
self.assertTrue(isinstance(p, SomeClass))
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.