1. 什么是doctest?
在Python中,我们经常需要编写测试代码,来确保程序的正确性。Python内置了一个名为doctest的测试模块,旨在测试Python模块的文档字符串。
doctest是Python的一个模块,它可以从文档字符串中提取测试用例,然后执行这些测试用例,从而验证代码是否正确。文档字符串是Python中的特殊注释,用三个引号括起来的一段字符串,通常放在函数定义的下面。在这个字符串中,我们可以写一些示例代码,doctest可以自动执行这些示例代码,并检验代码的输出是否与期望值一致。
2. doctest的基本用法
doctest最简单的用法就是在代码的文档字符串中添加一些示例代码。这些示例代码可以是函数的调用示例,也可以是模块的使用示例。例如,下面是一个简单的doctest示例,其中包含了一个函数和一个模块:
def average(numbers):
"""
Return the average of a sequence of numbers.
Examples:
>>> average([1, 2, 3, 4, 5])
3.0
>>> average([10, 20, 30, 40, 50])
30.0
"""
return sum(numbers) / len(numbers)
if __name__ == '__main__':
import doctest
doctest.testmod()
在这个示例中,我们定义了一个函数average,它计算一个数字列表的平均值。在函数的文档字符串中,我们使用了三个引号括起来的一段字符串,其中包含两个示例:average([1, 2, 3, 4, 5])应该返回3.0,average([10, 20, 30, 40, 50])应该返回30.0。然后,我们使用if __name__ == '__main__'语句来指定当脚本作为主程序运行时需要执行的代码,这里使用了doctest.testmod()函数来执行doctest测试。
3. doctest的常用选项
doctest还提供了许多选项来控制测试的行为。下面我们来介绍其中一些常用的选项。
3.1. VERBOSE
VERBOSE选项用于指定测试结果的输出方式,如果将VERBOSE设置为True,doctest会输出测试用例的详细信息,显示哪些测试用例通过了,哪些没有通过。例如,下面的脚本将会输出详细的测试结果:
import doctest
def add(x, y):
"""
>>> add(1,2)
3
>>> add('hello','world')
'helloworld'
"""
return x + y
if __name__ == '__main__':
doctest.testmod(verbose=True)
输出结果如下所示:
Trying:
add(1,2)
Expecting:
3
ok
Trying:
add('hello','world')
Expecting:
'helloworld'
ok
1 items had no tests:
__main__
1 items passed all tests:
2 tests in __main__.add
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
输出的结果是经过详细解释的。我们可以看到,doctest首先执行了第一个示例,输入参数(1, 2),调用函数add(1, 2),得到的结果是3,测试通过了。然后,doctest继续执行第二个示例,输入参数('hello', 'world'),调用函数add('hello', 'world'),得到的结果是'helloworld',测试也通过了。
3.2. ELLIPSIS
ELLIPSIS选项用于在测试中忽略某些输出内容。如果将ELLIPSIS设置为True,doctest会将输出中的一些内容替换为省略号。这个选项通常用于测试函数生成的长字符串。例如,我们来看一个简单的例子:
def generate_hello_world_string():
"""
>>> generate_hello_world_string()
'Hello, world!'
>>> generate_hello_world_string() # doctest: +ELLIPSIS
'Hello, ...!'
"""
return 'Hello, world!'
在这个示例中,我们定义了一个函数generate_hello_world_string,它生成一个字符串'Hello, world!'。在示例中,我们使用了两个测试用例来测试这个函数。第一个测试用例要求函数生成字符串'Hello, world!',而第二个测试用例添加了doctest: +ELLIPSIS选项,表示我们要忽略'world'之后的内容。这个选项通常用于测试一些字符串,例如HTML代码、XML代码等。
3.3. NORMALIZE_WHITESPACE
NORMALIZE_WHITESPACE选项用于在测试中忽略一些空白字符,例如空格和制表符。如果将NORMALIZE_WHITESPACE设置为True,doctest会将输出中的空格和制表符替换为单个空格。这个选项通常用于测试生成的文本文件、HTML文件等。例如,我们来看一个简单的例子:
def generate_html():
"""
>>> generate_html()
'<html><body><p>Hello, world!</p></body></html>'
>>> generate_html() # doctest: +NORMALIZE_WHITESPACE
'<html> <body> <p> Hello, world! </p> </body> </html>'
"""
return '<html><body><p>Hello, world!</p></body></html>'
在这个示例中,我们定义了一个函数generate_html,它生成一个HTML文件。在示例中,我们使用了两个测试用例来测试这个函数。第一个测试用例要求函数生成一个带有HTML标记的字符串,而第二个测试用例添加了doctest: +NORMALIZE_WHITESPACE选项,表示我们要忽略字符串中的一些空白字符。
4. doctest的高级用法
除了基本用法和常用选项外,doctest还提供了一些高级用法,例如在测试中使用上下文管理器、使用subTest、使用SKIP条件、使用命令行参数等。
4.1. 使用上下文管理器
在函数或方法中使用上下文管理器是一种常见的编程模式。在Python中,使用with语句可以很方便地管理上下文。doctest也支持在测试中使用上下文管理器。例如,下面是一个示例,其中包含了一个具有上下文管理器的函数:
import doctest
def read_file(filename):
"""
>>> with open('test.txt', 'w') as f:
... f.write('hello\\nworld\\n')
>>> read_file('test.txt')
'hello\\nworld\\n'
"""
with open(filename) as f:
return f.read()
if __name__ == '__main__':
doctest.testmod()
在这个示例中,我们定义了一个函数read_file,它读取一个文件的内容,并返回这个内容。在函数的文档字符串中,我们使用with语句来创建一个文件,并将一些字符串写入文件中。然后,我们使用read_file函数来读取这个文件的内容,并将其与期望值进行比较。
4.2. 使用subTest
在测试中使用subTest可以方便地测试多个实例。subTest是Python3.4版本中新增的一个功能,它可以将多个测试放在一个测试用例中执行,并显示每个测试的名称和状态。例如,下面是一个示例,其中使用了subTest:
import doctest
def divide(x, y):
"""
>>> divide(10, 2)
5.0
>>> divide(10, 0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
>>> divide(0, 10)
0.0
"""
try:
result = x / y
except ZeroDivisionError:
raise
else:
return result
if __name__ == '__main__':
with doctest.Example('*'):
for data in [(10, 2, 5.0), (10, 0, ZeroDivisionError), (0, 10, 0.0)]:
with doctest.subTest(*data):
d = divide(data[0], data[1])
if isinstance(data[2], type):
assert isinstance(d, data[2])
else:
assert d == data[2]
在这个示例中,我们定义了一个函数divide,它计算两个数之间的商。在函数的文档字符串中,我们使用了三个测试用例来测试这个函数。然后,我们使用with doctest.Example('*')来指定每个测试用例的名称,这里将测试用例的名称设置为'*'。这里使用with语句来管理测试用例的执行。
接下来,我们使用for循环来遍历多个数据,然后使用with doctest.subTest(*data)将每个数据包装为一个测试用例。最后,我们使用assert语句来判断测试是否通过。
4.3. 使用SKIP条件
在测试中,有时候需要跳过一些测试。doctest提供了SKIP条件来实现这个功能。SKIP条件用于指定一些条件,如果这些条件满足,就跳过测试。例如,下面是一个示例,其中使用了SKIP条件:
import sys
import doctest
def add(x, y):
"""
>>> add(1, 2)
3
>>> add(3, 4)
7
>>> add(5, 6)
Traceback (most recent call last):
...
ValueError: value out of range
"""
if sys.version_info.major == 2:
raise ValueError('value out of range')
return x + y
if __name__ == '__main__':
doctest.testmod(optionflags=doctest.SKIP)
在这个示例中,我们定义了一个函数add,它计算两个数的和。在函数的文档字符串中,我们使用了三个测试用例来测试这个函数。然后,我们使用了doctest.SKIP选项来跳过测试。
4.4. 使用命令行参数
在运行doctest时,可以指定一些命令行参数来控制测试的行为。例如,可以使用-v选项来显示详细的测试结果。例如,下面是一个示例,其中使用了命令行参数:
import doctest
def add(x, y):
"""
This function adds two numbers.
Examples:
>>> add(1, 2)
3
>>> add(3, 4)
7
"""
return x + y
if __name__ == '__main__':
import sys
args = [sys.argv[0], '-v']
doctest.testmod(argv=args)
在这个示例中,我们定义了一个函数add,它计算两个数的和。在函数的文档字符串中,我们使用了两个测试用例来测试这个函数。然后,我们使用sys.argv指定命令行参数,将-v选项传递给testmod函数。
5. 总结
本文介绍了Python中的doctest测试模块的具体用法,包括doctest的基本用法、常用选项以及高级用法。doctest是Python内置的测试模块,它可以从文档字符串中提取测试用例进行测试,非常方便。在编写Python程序时,建议将测试代码与程序代码一同编写,以确保程序的正确性。