Як ви перевіряєте, що функція 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
Raymond Wachaga

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

476

Оскільки Python 2.7, ви можете використовувати контекстний менеджер, щоб отримати фактичний викинутий об’єкт винятку:

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)).
Sohail Si

4
Дві речі: 1. Ви можете використовувати assertIn замість assertTrue. Наприклад, self.assertIn ("Це порушено", context.exception) 2. У моєму випадку, використовуючи 2.7.10, здається, що контекст.exception є масивом символів. Використання str не працює. Я закінчила це: '' .join (context.exception) Отже, складіть: self.assertIn ('Це порушено', '' .join (context.exception))
blockcipher

1
Це нормально, що ваш метод засмічує тестову консоль винятком Traceback? Як я запобігти цьому?
MadPhysicist

1
пізніше я знайшов інший спосіб отримати повідомлення як str винятку, це err = context.exception.message. Після цього можна також використати self.assertEqual (помилка, "це порушено"), щоб зробити тест.
Чжихонг

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>

І ми бачимо, що, як ми очікуємо, намагаємося додати а 1та '1'результат у TypeError.


Для отримання більш детального виводу, спробуйте це:

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

56

Ваш код повинен відповідати цій схемі (це тест модульного стилю модулів):

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ви отримаєте ПОМИЛУ замість FAIL.
розблоковується

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 піднімається актуально і створює помилку тесту. Проблема полягає в тому, що саме такої поведінки ми хотіли: s.

Щоб уникнути цієї помилки, просто запустіть функцію за допомогою лямбда в тестовому виклику:

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

Кінцевий вихід:

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

OK

Ідеально!

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

Танськ багато містера Жульєна Ленгранда-Ламберта


Це твердження про те, що фактично повертає помилковий позитив . Це трапляється тому, що лямбда всередині 'assrtRaises' є одиницею, яка викликає помилку типу, а не тестовану функцію.


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

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)

-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.