第 10 章 使用Python库进行单元测试
为了有效处理程序缺陷并保持高质量的代码,一组可以运行的测试是非常有用的,这些测试可以告诉我们代码是否按照预期正常工作。
Python标准库包含的unittest模块用于测试。这个模块不仅仅有有助于单元测试,可用于各种自动化测试的框架,从验收测试到集成测试再到单元测试都可用它。
与其他语言的测试框架一样,它的主要功能是帮助你进行自动化和可重复的测试。通过这样的测试,你可以随时很容易并且很轻松地验证代码是否正常工作。
10.1 测试用例
unittest模块的核心概念是测试用例(test case)。
测试用例(包含在unittest.TestCase类中)是将一组相关的测试方法组合在一起,它是unittest框架中测试组织的基本单元。单独的测试方法在unittest.TestCase子类中以方法的形式实现。
10.2 固件
固件(fixture)是在每个测试方法之前和/或之后运行的代码片段。固件有两个主要目的。
- 装配(set-up)固件确保在测试运行之前测试环境处于预期状态;
- 拆卸(tear-down)固件在测试运行后清理环境,通常是释放资源。
固件不是测试所必需的,但它们非常普遍,而且它们对于测试的可重复性是非常重要的。
10.3 断言
断言(assertion)。断言是测试方法内部的特定检查,最终确定测试是通过还是失败。除了这些功能外,断言可以:
- 进行简单的布尔检查;
- 执行对象相等性测试;
- 验证是否抛出了适当的异常。
如果断言失败,则测试方法失败,因此断言代表着你可以执行的最低级别的测试。可以在unittest文档中找到完整的断言列表。
10.4 单元测试示例:文本分析
下面使用测试驱动开发(test-driven development)来编写一个简单的文本分析函数。这个函数将文件名作为唯一的参数。然后,它将读取该文件并计算:
- 文件中的行数;
- 文件中的字符数。
TDD(测试驱动开发,Test-Driven Development)是一个迭代的开发过程,它不能在REPL上进行,将测试代码放在名为text_analyzer.py的文件中。先创建第一个测试,它只有支持正常运行的代码:
# text_analyzer.py
import unittest
class TextAnalysisTests(unittest.TestCase):
"""'analyze_text()'函数的测试"""
def test_function_runs(self):
"""基本的冒烟测试:运行函数"""
analyze_text()
if __name__ == '__main__':
unittest.main()
该段代码做的第一件事是导入unittest模块。然后通过定义一个类——TextAnalysisTests来创建测试用例,该类继承自unittest.TestCase。这就是用unittest框架创建测试用例的方法。
要在测试用例中定义单独的测试方法,可以 简单地在TestCase子类中创建以test_开头的方法。unittest框架在执行时会自动发现这样的方法,所以不需要明确地注册测试方法。
在这种情况下,检查analyze_text()函数是否运行。这个测试没有做任何明确的检查,而是依赖于这样一个事实:一个测试方法如果抛出异常,那么它就失败了。此时,如果不定义analyze_text(),那么测试将会失败。
最后,定义了惯用的main代码块,main代码块会在模块执行时调用unittest.main(),这个函数将搜索模块中的所有TestCase子类并执行所有的测试方法。
10.4.1 运行初始化测试
由于使用的是测试驱动设计,所以期望测试一开始就会失败。实际上,测试失败的原因很简单——还没有定义analyse_text():
PS D:\python\写给程序员的Python教程\pyfund> py text_analyzer.py
E
======================================================================
ERROR: test_function_runs (__main__.TextAnalysisTests.test_function_runs)
基本的冒烟测试:运行函数
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\python\写给程序员的Python教程\pyfund\text_analyzer.py", line 11, in test_function_runs
analyze_text()
^^^^^^^^^^^^
NameError: name 'analyze_text'