Як записати тест на одиницю, який не працює, лише якщо функція не кидає очікуваного винятку?
Як записати тест на одиницю, який не працює, лише якщо функція не кидає очікуваного винятку?
Відповіді:
Використовуйте TestCase.assertRaises
(або TestCase.failUnlessRaises
) з модуля unittest, наприклад:
import mymod
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc)
myfunc
вам потрібно додати їх як аргументи до виклику assertRaises. Дивіться відповідь Дарила Спітцер.
self.assertRaises(TypeError, mymod.myfunc)
. Повний список вбудованих винятків ви можете знайти тут: docs.python.org/3/library/exceptions.html#bltin-exceptions
self.assertRaises(SomeCoolException, Constructor, arg1)
Оскільки 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))
context.exception
не дає повідомлення; це тип.
import unittest2
, потрібно використовувати str()
, тобто 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)
2.7.15
. Якщо afunction
в self.assertRaises(ExpectedException, afunction, arg1, arg2)
- ініціалізатор класу, вам потрібно передати self
як перший аргумент, наприклад, self.assertRaises(ExpectedException, Class, self, 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 ви можете встановити резервний порт із 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))
Ваш код повинен відповідати цій схемі (це тест модульного стилю модулів):
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
перевіряє лише якщо було винято виняток.
assertRaises
ви отримаєте ПОМИЛУ замість FAIL.
від: 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' є одиницею, яка викликає помилку типу, а не тестовану функцію.
self.assertRaises(TypeError, df.square_value(self.false_int))
викликає метод і повертає результат. Те, що ви хочете, - це передати метод та будь-які аргументи та дозволити одиниці тестувати його зателефонувати:self.assertRaises(TypeError, df.square_value, self.false_int)
Ви можете створити власну, 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
модуля!
Я використовую 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
Щойно я виявив, що бібліотека Mock надає метод assertRaisesWithMessage () (у своєму підкласі unittest.TestCase), який перевірить не лише те, що очікуване виняток піднято, а й те, що він підвищений із очікуваним повідомленням:
from testcase import TestCase
import mymod
class MyTestCase(TestCase):
def test1(self):
self.assertRaisesWithMessage(SomeCoolException,
'expected message',
mymod.myfunc)
Тут є багато відповідей. Код показує, як ми можемо створити Виняток, як ми можемо використовувати цей виняток у наших методах та, нарешті, як ви можете перевірити в одиничному тесті, коли правильні винятки піднімаються.
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()
Ви можете використовувати 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)
Хоча всі відповіді ідеально чудові, я шукав спосіб перевірити, чи функція піднімає виняток, не покладаючись на рамки тестування одиниць і не потрібно писати тестові класи.
Я закінчив писати наступне:
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