How do you test that a Python function throws an exception?
如何编写只有在函数没有抛出预期异常时才会失败的单元测试?
使用unittest模块中的
1 2 3 4 5 | import mymod class MyTestCase(unittest.TestCase): def test1(self): self.assertRaises(SomeCoolException, mymod.myfunc) |
由于Python 2.7,你可以使用上下文管理器来获取实际抛出的异常对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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中,必须将
1 | self.assertTrue('This is broken' in str(context.exception)) |
我之前的答案中的代码可以简化为:
1 2 | def test_afunction_throws_exception(self): self.assertRaises(ExpectedException, afunction) |
如果一个函数接受争论,就把它们变成这样的断言:
1 2 | def test_afunction_throws_exception(self): self.assertRaises(ExpectedException, afunction, arg1, arg2) |
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?
短答:
使用
1 2 3 | def test_1_cannot_add_int_and_str(self): with self.assertRaises(TypeError): 1 + '1' |
示范
最佳实践方法很容易在Python shell中演示。
在Python 2.7或3中:
1 | import unittest |
在Python 2.6中,可以安装2.7的
1 | import unittest2 as unittest |
示例测试
现在,将以下测试Python类型安全性的测试粘贴到Python shell中:
1 2 3 4 5 6 7 | 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使用
我们也可以在没有上下文管理器的情况下编写它,参见测试2。第一个参数是您希望引发的错误类型,第二个参数是您正在测试的函数,其余的args和关键字args将传递给该函数。
我认为仅仅使用上下文管理器就会更加简单、可读和可维护。
运行的测试
运行测试:
1 | unittest.main(exit=False) |
在Python 2.6中,您可能需要以下内容:
1 | unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase)) |
你的终端机应输出以下内容:
1 2 3 4 5 6 | .. ---------------------------------------------------------------------- Ran 2 tests in 0.007s OK <unittest2.runner.TextTestResult run=2 errors=0 failures=0> |
正如我们所期望的,尝试添加一个
要获得更详细的输出,请尝试以下操作:
1 | unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase)) |
您的代码应该遵循以下模式(这是一个unittest模块风格的测试):
1 2 3 4 5 6 7 8 9 | 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函数
来自:http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/
首先,这里是文件dum_function.py中对应的(still dum:p)函数:
1 2 3 4 5 6 7 8 9 10 | 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 |
下面是要执行的测试(只插入这个测试):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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() |
我们现在准备测试我们的功能!下面是运行测试时发生的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ====================================================================== 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) |
类型错误是actullay引发的,并生成测试失败。问题是,这正是我们想要的行为:s。
为了避免这个错误,只需在测试调用中使用lambda运行函数:
1 | self.assertRaises(TypeError, lambda: df.square_value(self.false_int)) |
最终输出:
1 2 3 4 | ---------------------------------------------------------------------- Ran 1 test in 0.000s OK |
完美!
…对我来说也是完美的!!
非常感谢Julien Lengrand-Lambert先生
您可以构建自己的
1 2 3 4 5 6 7 8 9 10 | import contextlib @contextlib.contextmanager def raises(exception): try: yield except exception as e: assert True else: assert False |
然后你可以像这样使用
1 2 3 4 5 | with raises(Exception): print"Hola" # Calls assert False with raises(Exception): raise Exception # Calls assert True |
如果您使用的是
例子:
1 2 3 | def test_div_zero(): with pytest.raises(ZeroDivisionError): 1/0 |
结果:
1 2 3 4 5 6 | 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 |
我几乎在所有地方都使用doctest[1],因为我喜欢同时记录和测试函数。
请看这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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子类),它不仅会检查预期的异常是否被引发,还会检查它是否被预期的消息引发:
1 2 3 4 5 6 7 8 9 | from testcase import TestCase import mymod class MyTestCase(TestCase): def test1(self): self.assertRaisesWithMessage(SomeCoolException, 'expected message', mymod.myfunc) |
您可以使用来自unittest模块的断言
1 2 3 4 5 6 7 8 9 10 11 | 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) |