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

StackOverflow https://stackoverflow.com/questions/129507

Вопрос

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

Это было полезно?

Решение

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

import mymod

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

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

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

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 .Тестовый пример.assertRaises


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

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

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

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

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

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

Как вы проверяете, что функция 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 вы можете установить бэкпорт версии 2.7 unittest библиотека, называемая единичный тест2, и просто назовите это как 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))

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

От: 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.

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

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

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

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

OK

Идеально !

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

Спасибо большое , мистер .Julien Lengrand-Lambert

Вы можете создать свой собственный 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] почти везде, потому что мне нравится тот факт, что я документирую и тестирую свои функции одновременно.

Взгляните на этот код:

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

Взгляните на Утверждения способ проведения unittest модуль.

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

from testcase import TestCase

import mymod

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

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

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

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()
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top