目录 | 上一节 (7.5 装璜办法 | [ 下一节 (8.2 日志)]()
8.1 测试
多测试,少调试(Testing Rocks, Debugging Sucks)
Python 的动静性质使得测试对大多数程序而言至关重要。编译器不会发现你的 bug,发现 bug 的惟一形式是运行代码,并确保尝试了所有的个性。
断言(Assertions)
assert
语句用于程序的外部查看。如果表达式不为真,则会触发 AssertionError
异样。
assert
语句语法:
assert <expression> [, 'Diagnostic message']
示例:
assert isinstance(10, int), 'Expected int'
assert
语句不利用于检查用户的输出(例如,在网页表单输出的数据)。assert
语句旨在用于外部查看或者用于不变量(invariant,始终为 True 的条件)。
契约式编程
契约式编程(contract programming)也称为契约式设计(Design By Contract),自在应用断言是一种软件设计办法。契约式编程规定软件设计人员应该为软件组件定义准确的接口标准。
例如,你能够在所有的函数输出中应用断言:
def add(x, y):
assert isinstance(x, int), 'Expected int'
assert isinstance(y, int), 'Expected int'
return x + y
如果函数调用者没有应用正确的参数,那么查看输出能够立刻捕捉到。
>>> add(2, 3)
5
>>> add('2', '3')
Traceback (most recent call last):
...
AssertionError: Expected int
>>>
内联测试
断言也能够用于简略的测试。
def add(x, y):
return x + y
assert add(2,2) == 4
这样,你就能够将测试与代码蕴含在同一模块中。
益处:如果代码显著被毁坏,那么尝试导入模块将会导致程序解体。
对于详尽的测试,不举荐这样做。这种做法更像是根本的“冒烟测试(smoke test)”。函数是否能够在所有的用例上失常工作?如果不能够,那么必定是有问题的。
unittest
模块
假如你有上面这样一段代码:
# simple.py
def add(x, y):
return x + y
当初,你想对这些代码进行测试,请创立一个独自的测试文件,如下所示:
# test_simple.py
import simple
import unittest
而后定义一个测试类:
# test_simple.py
import simple
import unittest
# Notice that it inherits from unittest.TestCase
class TestAdd(unittest.TestCase):
...
测试类必须继承自 unittest.TestCase
。
在测试类中,定义测试方法:
# test_simple.py
import simple
import unittest
# Notice that it inherits from unittest.TestCase
class TestAdd(unittest.TestCase):
def test_simple(self):
# Test with simple integer arguments
r = simple.add(2, 2)
self.assertEqual(r, 5)
def test_str(self):
# Test with strings
r = simple.add('hello', 'world')
self.assertEqual(r, 'helloworld')
重要提醒:每个办法的名称必须以 test
结尾。
应用 unittest
unittest
中内置了一些断言,每种断言对不同的事件进行诊断。
# Assert that expr is True
self.assertTrue(expr)
# Assert that x == y
self.assertEqual(x,y)
# Assert that x != y
self.assertNotEqual(x,y)
# Assert that x is near y
self.assertAlmostEqual(x,y,places)
# Assert that callable(arg1,arg2,...) raises exc
self.assertRaises(exc, callable, arg1, arg2, ...)
上述列表并不是一个残缺的列表,unittest
模块还有其它断言。
运行 unittest
要运行测试,请把代码转换为脚本。
# test_simple.py
...
if __name__ == '__main__':
unittest.main()
而后应用 Python 执行测试文件:
bash % python3 test_simple.py
F.
========================================================
FAIL: test_simple (__main__.TestAdd)
--------------------------------------------------------
Traceback (most recent call last):
File "testsimple.py", line 8, in test_simple
self.assertEqual(r, 5)
AssertionError: 4 != 5
--------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=1)
阐明
高效的单元测试是一种艺术。对于大型利用而言,单元测试可能会变得非常复杂。
unittest
模块具备大量与测试运行器(test runners),测试后果集(collection of results)以及测试其余方面相干的选项。相干详细信息,请查阅文档。
第三方测试工具
尽管内置 unittest
模块的劣势是能够随处应用——因为它是 Python 的一部分,然而许多程序员也感觉 unittest
十分繁琐。另一个风行的的测试工具是 pytest。应用 pytest,测试文件能够简化为以下模式:
# test_simple.py
import simple
def test_simple():
assert simple.add(2,2) == 4
def test_str():
assert simple.add('hello','world') == 'helloworld'
要运行测试,只须要输出一个命令即可,例如:python -m pytest
。它将会发现所有的测试并运行这些测试。
除了这个示例之外,pytest
还有很多内容。如果你决定尝试一下,通常很容易上手。
练习
在本次练习中,咱们将摸索应用 Python unittest
模块的根本机制(mechanics)。
在后面的练习中,咱们编写了一个蕴含 Stock
类的 stock.py
文件。对于本次练习,假如咱们应用的是 练习 7.9 中编写的与类型化属性相干的代码(译注:typedproperty.py
)。如果因为某些起因,练习 7.9 的代码无奈失常工作,你能够从 Solutions/7_9
中复制 typedproperty.py
到工作目录中。
练习 8.1:编写单元测试
请创立一个独自的 test_stock.py
文件,为 Stock
编写单元测试集。为了让你入门,这里有一小段测试实例创立的代码:
# test_stock.py
import unittest
import stock
class TestStock(unittest.TestCase):
def test_create(self):
s = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(s.name, 'GOOG')
self.assertEqual(s.shares, 100)
self.assertEqual(s.price, 490.1)
if __name__ == '__main__':
unittest.main()
运行单元测试,你应该能够取得一些像上面这有的输入:
.
----------------------------------------------------------------------
Ran 1 tests in 0.000s
OK
而后,编写其它单元测试来查看以下各项内容:
- 确保
s.cost
属性返回正确的值(49010.0)。 - 确保
s.sell()
办法失常工作。它应该相应地减小s.shares
。 - 确保
s.shares
属性只能设置为整数值。
对于最初一部分,你须要查看异样的触发。要做到这些,一种简略的办法是应用如下代码:
class TestStock(unittest.TestCase):
...
def test_bad_shares(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(TypeError):
s.shares = '100'
目录 | 上一节 (7.5 装璜办法 | [ 下一节 (8.2 日志)]()
注:残缺翻译见 https://github.com/codists/practical-python-zh