Как вы проверяете, что функция Python генерирует исключение? - PullRequest
627 голосов
/ 25 сентября 2008

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

Ответы [ 11 ]

544 голосов
/ 25 сентября 2008

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

import mymod

class MyTestCase(unittest.TestCase):
    def test1(self):
        self.assertRaises(SomeCoolException, mymod.myfunc)
391 голосов
/ 02 июля 2010

Начиная с 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))
261 голосов
/ 25 сентября 2008

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

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

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

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction, arg1, arg2)
112 голосов
/ 29 января 2015

Как проверить, что функция 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')

Test one использует 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))
41 голосов
/ 25 сентября 2008

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

def test_afunction_throws_exception(self):
    try:
        afunction()
    except ExpectedException:
        pass
    except Exception as e:
       self.fail('Unexpected exception raised:', e)
    else:
       self.fail('ExpectedException not raised')

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

14 голосов
/ 05 февраля 2013

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

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

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

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

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

OK

Отлично!

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

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

10 голосов
/ 21 мая 2014

Вы можете создать свой собственный 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
10 голосов
/ 25 сентября 2008

Я использую 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

9 голосов
/ 25 сентября 2008

Посмотрите на метод assertRaises модуля unittest.

6 голосов
/ 28 октября 2008

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

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...