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

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

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
892
0
531 810
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Ответ принят как подходящий

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

import mymod

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

есть ли способ сделать наоборот? Как будто он терпит неудачу, только если функция генерирует исключение?

BUInvent 26.07.2017 22:44

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

cbron 21.02.2018 19:43

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

Dinesh 30.10.2018 07:20

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

Raymond Wachaga 07.02.2019 20:51

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

tschumann 08.06.2019 08:49

другой способ передачи аргументов - self.assertRaises(MyException, lambda: do_something(with=some, arguments))

jemand771 05.03.2020 11:06

Чтобы ответить на вопрос BUInvent, как сделать наоборот: просто позвоните mymod.myfunc() из своего теста. Если он возникает, это исключение не пройдет проверку. Это поведение по умолчанию для всего, что вы вызываете из теста.

Jonathan Hartley 11.07.2020 00:32

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

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

mdob 15.10.2012 01:54

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

unflores 21.01.2015 15:52

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

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

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

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

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

Sabyasachi 19.06.2018 11:07

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

Minh Tran 24.08.2018 18:31

Я использую доктест [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

Мне нравится doctest, но я считаю, что он скорее дополняет, чем заменяет unittest.

TimothyAWiseman 06.10.2012 01:17

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

kdbanman 15.06.2015 19:37

Я только что обнаружил, что Библиотека макетов предоставляет метод 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 31.01.2013 18:23

Начиная с 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))

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

LateCoder 25.02.2016 19:55

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

Sohail Si 19.01.2017 04:00

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

blockcipher 07.02.2017 17:38

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

MadPhysicist 15.03.2017 17:10

Сейчас я использую 2.7.12, и он работает. Это правда, что context.exception по-прежнему является типом, но он работает, str (context.exception) не требуется.

zhihong 11.05.2017 13:45

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

zhihong 11.05.2017 14:04

Или, если вас не волнует сообщение об исключении, вы можете упростить его до: with self.assertRaises(Exception): broken_function()

outofthecave 19.05.2020 18:52

Попробуйте вместо этого self.assertTrue («Это не работает» в context.exception.value).

msashish 08.07.2020 16:18

Вы можете использовать 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)

от: 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 возникает фактически и вызывает сбой теста. Проблема в том, что это именно то поведение, которое мы хотели: s.

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

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

Окончательный результат:

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

OK

Идеальный !

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

Спасибо большое, мистер Жюльен Ленгран-Ламбер.


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

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

Roman Kutlak 08.08.2013 16:36

Вы можете создать свой собственный 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

Благодарим за отправку ответа, для которого не требуется модуль unittest!

Sherwood Callaway 06.01.2019 01:10

How do you test that a Python function throws an exception?

How does one write a test that fails only if a function doesn't throw an expected exception?

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

Используйте метод 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>

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


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

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

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

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()

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

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

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

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

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

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

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

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

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

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

# Here I check actual exception 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])

Этот ответ исключительно (ха!) Полезен из-за проверки типа и предложения проверки чего-либо еще. Это помогло мне проверить явное сообщение об ошибке с self.assertEqual(e.exception.args[0], "Values cannot be a generator. Use list(generator) instead.",).

Steve Piercy 03.09.2020 07:05

Ошибки, управляемые контекстом, являются лучшими. Они также позволяют проверить сообщение об ошибке!

Florian Fasmeyer 20.11.2020 13:13

Вы можете использовать диспетчер контекста для запуска неисправной функции и утверждения, что она вызывает исключение с определенным сообщением, используя assertRaisesMessage.

with self.assertRaisesMessage(SomeException,'Some error message e.g 404 Not Found'):
    faulty_funtion()

assertRaisesMessage - это метод только Django, а не собственный метод класса Python Testcase, как показано в документах здесь, пожалуйста, отредактируйте свой ответ, чтобы прояснить это.

Matthew Barlowe 31.12.2020 00:45

Для await / async aiounittest существует немного другой шаблон:

https://aiounittest.readthedocs.io/en/latest/asynctestcase.html#aiounittest.AsyncTestCase

async def test_await_async_fail(self):
    with self.assertRaises(Exception) as e:
        await async_one()

Другие вопросы по теме