Как вы проверяете, что функция Python генерирует исключение?


Ответы:


679

Используйте TestCase.assertRaises(или TestCase.failUnlessRaises) из модуля unittest, например:

import mymod

class MyTestCase(unittest.TestCase):
    def test1(self):
        self.assertRaises(SomeCoolException, mymod.myfunc)

9
Есть ли способ сделать противоположное этому? Вроде не получится, только если функция выдает исключение?
BUINvent

67
Обратите внимание, что для передачи аргументов myfuncвам необходимо добавить их в качестве аргументов в вызов assertRaises. Смотрите ответ Дэрила Спитцера.
cbron

1
Есть ли способ разрешить несколько типов исключений?
Динеш

1
Вы можете использовать встроенные исключения Python, чтобы быстро проверить утверждение; используя @ ответ Мо выше, например: self.assertRaises(TypeError, mymod.myfunc). Вы можете найти полный список встроенных исключений здесь: docs.python.org/3/library/exceptions.html#bltin-exceptions
Раймонд Вачага,

3
То же самое для тестирования конструкторов классов:self.assertRaises(SomeCoolException, Constructor, arg1)
tschumann

477

Начиная с Python 2.7 вы можете использовать менеджер контекста, чтобы получить реальный объект Exception:

import unittest

def broken_function():
    raise Exception('This is broken')

class MyTestCase(unittest.TestCase):
    def test(self):
        with self.assertRaises(Exception) as context:
            broken_function()

        self.assertTrue('This is broken' in context.exception)

if __name__ == '__main__':
    unittest.main()

http://docs.python.org/dev/library/unittest.html#unittest.TestCase.assertRaises


В Python 3.5 , вы должны обернуть context.exceptionв strпротивном случае вы получитеTypeError

self.assertTrue('This is broken' in str(context.exception))

6
Я использую Python 2.7.10, и выше не работает; context.exceptionне дает сообщение; это тип.
LateCoder

6
Кроме того, в Python 2.7 (по крайней мере , в моем 2.7.6) с использованием import unittest2, вам нужно использовать str(), то есть self.assertTrue('This is broken' in str(context.exception)).
Сохаил Си

4
Две вещи: 1. Вы можете использовать assertIn вместо assertTrue. Например, self.assertIn («Это не работает», context.exception) 2. В моем случае, используя 2.7.10, context.exception выглядит как массив символов. Использование str не работает. Я закончил тем, что сделал это: '' .join (context.exception) Итак, соберите вместе: self.assertIn ('Это сломано', '' .join (context.exception))
blockcipher

1
Это нормально, что ваш метод забивает тестовую консоль с помощью Traceback исключения? Как я могу предотвратить это?
MadPhysicist

1
позже я нашел другой способ получить сообщение как str исключения, это err = context.exception.message. И затем можно использовать также использовать self.assertEqual (err, 'Это не работает'), чтобы сделать тест.
Чжихун

326

Код в моем предыдущем ответе может быть упрощен до:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction)

И если функция принимает аргументы, просто передайте их в assertRaises следующим образом:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction, arg1, arg2)

17
Второй отрывок о том, что делать при передаче аргумента, был действительно полезным.
Сабьясачи

Я использую 2.7.15. Если afunctionв self.assertRaises(ExpectedException, afunction, arg1, arg2)это инициализатор класса, вам нужно передать selfв качестве первого аргумента, например, self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Minh Tran

128

Как вы проверяете, что функция Python генерирует исключение?

Как написать тест, который не проходит, только если функция не выдает ожидаемое исключение?

Короткий ответ:

Используйте self.assertRaisesметод как менеджер контекста:

    def test_1_cannot_add_int_and_str(self):
        with self.assertRaises(TypeError):
            1 + '1'

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

Подход наилучшей практики довольно легко продемонстрировать в оболочке Python.

unittestбиблиотека

В Python 2.7 или 3:

import unittest

В Python 2.6 вы можете установить бэкпортunittest библиотеки 2.7 , называемый unittest2 , и просто псевдоним, который будет следующим unittest:

import unittest2 as unittest

Пример тестов

Теперь вставьте в вашу оболочку Python следующий тест безопасности типов Python:

class MyTestCase(unittest.TestCase):
    def test_1_cannot_add_int_and_str(self):
        with self.assertRaises(TypeError):
            1 + '1'
    def test_2_cannot_add_int_and_str(self):
        import operator
        self.assertRaises(TypeError, operator.add, 1, '1')

Тест один использует assertRaises в качестве диспетчера контекста, который гарантирует, что ошибка правильно перехвачена и исправлена ​​во время записи.

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

Я думаю, что гораздо проще, удобочитаемее и удобнее в использовании просто использование диспетчера контекста.

Запуск тестов

Чтобы запустить тесты:

unittest.main(exit=False)

В Python 2.6 вам, вероятно, понадобится следующее :

unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))

И ваш терминал должен вывести следующее:

..
----------------------------------------------------------------------
Ran 2 tests in 0.007s

OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>

И мы видим, что, как мы и ожидаем, попытка добавить a 1и '1'результат в TypeError.


Для более подробного вывода попробуйте это:

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))

56

Ваш код должен следовать этому шаблону (это тест стиля модуля unittest):

def test_afunction_throws_exception(self):
    try:
        afunction()
    except ExpectedException:
        pass
    except Exception:
       self.fail('unexpected exception raised')
    else:
       self.fail('ExpectedException not raised')

На Python <2.7 эта конструкция полезна для проверки конкретных значений в ожидаемом исключении. Функция unittest assertRaisesтолько проверяет, возникло ли исключение.


3
и метод self.fail принимает только один аргумент
mdob

3
Это кажется слишком сложным для тестирования, если функция выдает исключение. Поскольку любое исключение, отличное от этого исключения, приведет к ошибке теста, а отказ от исключения приведет к неудаче теста, похоже, единственное отличие состоит в том, что если вы получите другое исключение, assertRaisesвы получите ОШИБКУ вместо ОТКАЗА.
открывается

23

от: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/

Во-первых, вот соответствующая (все еще dum: p) функция в файле dum_function.py:

def square_value(a):
   """
   Returns the square value of a.
   """
   try:
       out = a*a
   except TypeError:
       raise TypeError("Input should be a string:")

   return out

Вот тест, который нужно выполнить (вставлен только этот тест):

import dum_function as df # import function module
import unittest
class Test(unittest.TestCase):
   """
      The class inherits from unittest
      """
   def setUp(self):
       """
       This method is called before each test
       """
       self.false_int = "A"

   def tearDown(self):
       """
       This method is called after each test
       """
       pass
      #---
         ## TESTS
   def test_square_value(self):
       # assertRaises(excClass, callableObj) prototype
       self.assertRaises(TypeError, df.square_value(self.false_int))

   if __name__ == "__main__":
       unittest.main()

Теперь мы готовы проверить нашу функцию! Вот что происходит при попытке запустить тест:

======================================================================
ERROR: test_square_value (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_dum_function.py", line 22, in test_square_value
    self.assertRaises(TypeError, df.square_value(self.false_int))
  File "/home/jlengrand/Desktop/function.py", line 8, in square_value
    raise TypeError("Input should be a string:")
TypeError: Input should be a string:

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

FAILED (errors=1)

TypeError вызывается actullay и генерирует тестовый сбой. Проблема в том, что это именно то поведение, которое мы хотели: с.

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

self.assertRaises(TypeError, lambda: df.square_value(self.false_int))

Окончательный вывод:

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

OK

Отлично !

... и для меня тоже идеально !!

Большое спасибо господин Жюльен Ленгранд-Ламберт


Этот тест assert фактически возвращает ложный положительный результат . Это происходит потому, что лямбда внутри 'assertRaises' является единицей, которая вызывает ошибку типа, а не проверенной функцией.


10
Просто заметка, вам не нужна лямбда. Строка self.assertRaises(TypeError, df.square_value(self.false_int))вызывает метод и возвращает результат. Вам нужно передать метод и любые аргументы и позволить юнит-тесту вызвать его:self.assertRaises(TypeError, df.square_value, self.false_int)
Роман Кутлак

Большое спасибо. отлично работает
Чандан Кумар

14

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

import contextlib

@contextlib.contextmanager
def raises(exception):
    try:
        yield 
    except exception as e:
        assert True
    else:
        assert False

И тогда вы можете использовать raisesкак это:

with raises(Exception):
    print "Hola"  # Calls assert False

with raises(Exception):
    raise Exception  # Calls assert True

Если вы используете pytest, эта вещь уже реализована. Ты можешь сделатьpytest.raises(Exception) :

Пример:

def test_div_zero():
    with pytest.raises(ZeroDivisionError):
        1/0

И результат:

pigueiras@pigueiras$ py.test
================= test session starts =================
platform linux2 -- Python 2.6.6 -- py-1.4.20 -- pytest-2.5.2 -- /usr/bin/python
collected 1 items 

tests/test_div_zero.py:6: test_div_zero PASSED

1
Спасибо за публикацию ответа, который не требует unittestмодуля!
Шервуд

10

Я использую doctest [1] почти везде, потому что мне нравится тот факт, что я документирую и тестирую свои функции одновременно.

Посмотрите на этот код:

def throw_up(something, gowrong=False):
    """
    >>> throw_up('Fish n Chips')
    Traceback (most recent call last):
    ...
    Exception: Fish n Chips

    >>> throw_up('Fish n Chips', gowrong=True)
    'I feel fine!'
    """
    if gowrong:
        return "I feel fine!"
    raise Exception(something)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

Если вы поместите этот пример в модуль и запустите его из командной строки, оба контрольных примера будут оценены и проверены.

[1] Документация по Python: 23.2 doctest - тестирование интерактивных примеров Python


4
Я люблю doctest, но считаю, что он дополняет, а не заменяет unittest.
TimothyAWiseman

2
Неужели doctest реже будет играть с автоматическим рефакторингом? Я полагаю, что инструмент рефакторинга, разработанный для python, должен знать о строках документов. Кто-нибудь может прокомментировать их опыт?
kdbanman 15.06.15

6

Я только что обнаружил, что библиотека Mock предоставляет метод assertRaisesWithMessage () (в своем подклассе unittest.TestCase), который будет проверять не только то, что вызывается ожидаемое исключение, но и то, что оно вызывается с ожидаемым сообщением:

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)

К сожалению, он этого больше не дает .. Но приведенный выше ответ @Art ( stackoverflow.com/a/3166985/1504046 ) дает тот же результат
Rmatt

6

Здесь много ответов. Код показывает, как мы можем создать Исключение, как мы можем использовать это исключение в наших методах и, наконец, как вы можете проверить в модульном тесте правильные возбуждаемые исключения.

import unittest

class DeviceException(Exception):
    def __init__(self, msg, code):
        self.msg = msg
        self.code = code
    def __str__(self):
        return repr("Error {}: {}".format(self.code, self.msg))

class MyDevice(object):
    def __init__(self):
        self.name = 'DefaultName'

    def setParameter(self, param, value):
        if isinstance(value, str):
            setattr(self, param , value)
        else:
            raise DeviceException('Incorrect type of argument passed. Name expects a string', 100001)

    def getParameter(self, param):
        return getattr(self, param)

class TestMyDevice(unittest.TestCase):

    def setUp(self):
        self.dev1 = MyDevice()

    def tearDown(self):
        del self.dev1

    def test_name(self):
        """ Test for valid input for name parameter """

        self.dev1.setParameter('name', 'MyDevice')
        name = self.dev1.getParameter('name')
        self.assertEqual(name, 'MyDevice')

    def test_invalid_name(self):
        """ Test to check if error is raised if invalid type of input is provided """

        self.assertRaises(DeviceException, self.dev1.setParameter, 'name', 1234)

    def test_exception_message(self):
        """ Test to check if correct exception message and code is raised when incorrect value is passed """

        with self.assertRaises(DeviceException) as cm:
            self.dev1.setParameter('name', 1234)
        self.assertEqual(cm.exception.msg, 'Incorrect type of argument passed. Name expects a string', 'mismatch in expected error message')
        self.assertEqual(cm.exception.code, 100001, 'mismatch in expected error code')


if __name__ == '__main__':
    unittest.main()

3

Вы можете использовать assertRaises из модуля unittest

import unittest

class TestClass():
  def raises_exception(self):
    raise Exception("test")

class MyTestCase(unittest.TestCase):
  def test_if_method_raises_correct_exception(self):
    test_class = TestClass()
    # note that you dont use () when passing the method to assertRaises
    self.assertRaises(Exception, test_class.raises_exception)

0

Поскольку я не видел подробного объяснения о том, как проверить, получено ли конкретное исключение из списка принятых с помощью контекстного менеджера, или других подробностей исключения, я добавлю свои (проверено на python 3.8).

Если я просто хочу проверить, например TypeError, что функция поднимается , я бы написал:

with self.assertRaises(TypeError):
    function_raising_some_exception(parameters)

Если я хочу проверить, что функция вызывает или TypeErrorили IndexError, я бы написал: с self.assertRaises ((TypeError, IndexError)): function_raising_some_exception (параметры)

И если я захочу узнать больше подробностей об Исключении, я могу уловить его в таком контексте:

# Here I catch any exception    
with self.assertRaises(Exception) as e:
    function_raising_some_exception(parameters)

# Here I check actual eception type (but I could check anything else about that specific exception, like it's actual message or values stored in the exception)
self.assertTrue(type(e.exception) in [TypeError,MatrixIsSingular])

-5

Несмотря на то, что все ответы в порядке, я искал способ проверить, вызвала ли функция исключение, не полагаясь на рамки модульного тестирования и не создавая тестовые классы.

В итоге я написал следующее:

def assert_error(e, x):
    try:
        e(x)
    except:
        return
    raise AssertionError()

def failing_function(x):
    raise ValueError()

def dummy_function(x):
    return x

if __name__=="__main__":
    assert_error(failing_function, 0)
    assert_error(dummy_function, 0)

И это не на правильной линии:

Traceback (most recent call last):
  File "assert_error.py", line 16, in <module>
    assert_error(dummy_function, 0)
  File "assert_error.py", line 6, in assert_error
    raise AssertionError()
AssertionError
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.