测试你的代码是非常重要的。
习惯于同时写测试用例和运行代码,现在被视为一个好的习惯。如果使用得当,这种方式将帮助你更加明确自己代码的功能,以及拥有更加可解耦的结构。
你应对自己编写的函数和类进行测试。这样你就能够更加确定自己所做的工作不会破坏项目的其他部分,你就能够随心所欲地改进既有的代码了。
如果你在项目中包含了初步测试用例,其他程序猿更敬佩你,更愿意work with you together ,他们将能够更得心应手地尝试使用你编写的代码,也更愿意与你合作开发项目。
测试的通用规则:
- 测试单元应该集中于最小部分功能,并且证明它是正确的。
- 每个测试单元必须完全独立。他们都能够单独运行,也可以在测试套件中运行,而不用考虑被调用的顺序。 要想实现这个规则,测试单元应该加载最新的数据集,之后再做一些清理。 这通常用方法
setUp()
和tearDown()
处理。 - 尽量使测试单元快速运行。如果一个单独的测试单元需要较长的时间去运行,开发进度将会延迟, 测试单元将不能如期常态性运行。有时候,因为测试单元需要复杂的数据结构, 并且当它运行时每次都要加载,所以其运行时间较长。请把运行速度较慢的测试单元放在单独的测试组件中, 并且按照需要运行其它测试单元。
- 学习使用工具,学习如何运行一个单独的测试或者测试用例。当为某个模块开发了一个新功能时, 我们需要经常运行这个功能的测试用例,理想情况下需配置工具让其在保存代码文件时自动触发运行测试。
- 在编码工作开始前后,请运行完整的测试组件。只有这样,你才会坚信现有的代码不会出现错误。
- 使用钩子(hook)是一个非常推荐的做法,一旦把代码提交到共享的代码仓库时(译者注:很多时候你会选择 Github), 即可触发钩子运行所有的测试。
- 如果你在开发期间不得不打断自己的工作,请先为你下一步要开发的功能写一个未通过的测试,这样当你回到工作时,将可以很快地回到原先被打断的地方,并且步入正轨。
当你调试代码的时候,首先需要写一个精确定位 Bug 的测试单元。尽管这样做很难, 但是捕捉 Bug 的单元测试在项目中很重要。
测试函数需使用长且描述性的名字。测试的编码规范与代码编码规范有点不一样,代码更倾向于使用短的名字, 而测试函数不会直接被调用。在运行代码中,
square()
或者甚至sqr()
这样的命名都是可以的, 但是在测试代码中,您应该这样取名test_square_of_number_2()
,test_square_negative_number()
。 当测试单元失败时,函数名会被直接显示出来,此时函数名称的描述性将变得重要。- 当业务逻辑不得不变更时,如果代码中有一套不错的测试单元, 维护者将很大一部分依靠测试组件解决问题,或者确保改动不会影响到其他代码。此时测试代码会经常被阅读, 阅读的频率甚至多于业务逻辑代码。目的不明确的测试单元在这种情况下没有多少用处,因此请尽量避免书写目的不明确的测试代码。
- 测试代码的另外一个用处是作为新开发人员的入门介绍。当有人需要基于现有的代码库工作时, 运行并且阅读相关的测试代码是最好的做法。他们会或者应该发现业务代码的重点、难点、以及边界场景。 如果他们必须添加一些功能,第一步应该是添加一个测试,以确保新功能开发能保持测试的传统。
入门
Unittest 单元测试
unittest
是 Python 标准库中自带的测试模块。任何一个使用过 Junit,nUnit, 或 CppUnit 工具的人对它的 API 都会比较熟悉。
我们可以通过继承 unittest.TestCase
来创建测试用例:
1 | import unittest |
Python 2.7 以后,unittest
已支持测试自动发现机制。
Doctest
doctest
模块会在代码的 Docstrings 中寻找类似于 Python 交互会话的字串(译者注:>>>
),并会将其执行,以证实工作正常。
Doctest 模块的使用场景与单元测试有所不同:它们通常不是很详细,并且不会用特别的用例或者处理复杂的 Bug。Doctest 主要是作为模块和其部件主要用例的表述性文档,因此,Doctest 需在每一次完整测试 套件运行时自动运行。
函数中的一个简单 Doctest 例子:
1 | def square(x): |
当使用 python module.py
这样的命令行运行这个模块时,Doctest 将会运行,并会在结果与文档字符串的描述不一致时报错。
其他工具
py.test
相比于 Python 标准库里的 unittest 模块,py.test 也是一个没有模板(no-boilerplate)的备选方案:
1 | $ pip install pytest |
尽管这个测试工具功能完备,并且可扩展,它仍然能保持语法很简单。创建一个测试组件和写一个带有诸多函数的模块一样容易:
1 | # content of test_sample.py |
运行命令 py.test :
1 | $ py.test |
可以看出,这要比 unittest 模块中实现相同功能所要求的工作量少得多。
Hypothesis
Hypothesis 让你编写被示例源码参数化的测试库。它会生成简单易懂的例子,使你的测试失败, 让你花更少的力气找到更多的错误。
1 | $ pip install hypothesis |
例如,测试浮动列表要尝试很多例子,但是会报告每个错误的最小例子(区分异常类型和位置):
1 | @given(lists(floats(allow_nan=False, allow_infinity=False), min_size=1)) |
Hypothesis 是实用且强大的工具,很多时候它都会找出被其他测试工具所遗漏的错误。 它能与 py.test 很好地集成,无论是简单亦或者是高级场景中,你都会觉得它很趁手。
tox
tox 是一个自动化测试环境管理,并能针对多版本解释器配置进行测试的工具。
1 | $ pip install tox |
tox 允许你通过简单的配置文件,来设置复杂的多参数测试矩阵。
Unittest2
Unittest2 是 Python 2.7 中 unittest 模块的向后兼容补丁,对比 Python 2.7 之前的版本提供了更好的 API 和断言语法。
如果使用 Python 2.6 版本或者以下,你可以使用 pip 安装 unittest2:
1 | $ pip install unittest2 |
推荐你使用 unittest 之名导入模块,目的是更容易地把代码移植到新的版本中:
1 | import unittest2 as unittest |
如果切换到新的 Python 版本,并且不再需要 unittest2 模块,你只需要在测试模块中改变 import 内容,而不必改变其它代码。
mock
unittest.mock
是 Python 中用于测试的一个库。 Python 3.3 版本中,将存在于自带的标准库中 —— 标准库中的 unittest.mock。
对于 Python 相对早的版本,如下操作:
1 | $ pip install mock |
在测试环境下,使用 mock 对象能够替换部分系统,并且对它们的使用进行断言。
例如,你可以对一个方法打猴子补丁:
1 | from mock import MagicMock |
在测试环境下,你可以使用 patch
修饰器来 mock 某个模块中的类或对象。在下面这个例子中,一直返回相同结果的外部查询系统使用 mock 替换(但仅用在测试期间)。
1 | def mock_search(self): |
Mock 还提供许多其它方法,你可以很轻松地配置和控制它的行为。