Python doctest模块 – 编写和测试代码文档
在本教程中,我们将学习doctest模块。它是一个测试框架,可以帮助我们同时编写和测试代码文档。这个模块允许我们记录和测试我们的代码,这对于编码来说是必不可少的。默认情况下,我们可以使用docstring来编写类描述或函数,以提供更好的代码理解。
我们不需要安装任何第三方软件来使用doctest模块。但是,我们应该具有基本的Python知识。
文档示例
记录代码是最佳实践,资深开发人员建议经常遵循这个做法。可以这样说,编码和其文档同等重要。Python提供了各种方法来记录项目、应用程序、模块或脚本。在较大的项目中通常需要外部文档,而较小的项目可以使用描述性的名称、注释和docstrings来进行文档资料。
"""The below function implements functions to process iterables."""
def get_value(num, iterable):
"""Return True if value is in iterable, False otherwise."""
# Be explicit by using iteration instead of membership
for val in iterable:
if num == val: # Get the target value by equality
return True
return False
正如我们所看到的,get_value()方法帮助我们获取给定的值。我们已经正确记录了上面的函数,这使其更易读和可维护。在上面的代码中,我们还使用了注释来解释代码的作用和原因。在Python中,注释以#符号开头,可以占据自己的行。
另一方面,注释也有一些缺点-
- 解释器或编译器忽略注释,使得它们在运行时不可读。
- 随着代码演变,注释和docstrings经常变得过时而没有改变。
文档字符串是Python的一个非常有用的特性,它可以帮助我们在编写代码时记录我们的代码。它比注释具有优势,因为解释器不会忽略它们。
docstring是代码的活动部分;我们可以在运行时访问它。Python在我们的软件包、模块、类、方法和函数上提供doc特殊属性。
Python允许在包、模块、类、方法和函数中包含docstrings。为了编写有效的docstrings,建议遵循PEP 257中概述的惯例和指南。
Python的doctest模块介绍
在本节中,您将介绍Python doctest模块。这个模块是一个标准库组件,不需要安装任何外部库以用于日常编码。您将学习关于doctest的内容,包括它的目的以及何时使用它。讨论将从doctest是什么的概述开始。
它是如何工作的
doctest是一个轻量级的测试框架,可以让我们快速简单地进行测试自动化。Python中的doctest模块可以从项目文档和代码docstrings中读取测试用例。这个框架与Python解释器一起打包,符合“含电池”的理念。
Doctest搜索类似于Python交互式会话的文本,然后将文本分成可执行代码和期望结果,运行代码,并将执行结果与期望结果进行比较。我们可以从代码或命令行使用doctest。
在Python中编写doctest测试(以下代码无法翻译)
def sum(x, y):
"""
This function helps us to add two numbers.
>>> sum(4,3)
7
>>> sum(2,8)
10
"""
return x + y
到目前为止,我们已经了解了doctest的基本知识。现在,我们将学习如何检查函数、方法和其他可调用对象的返回值。我们还将学习如何为代码创建测试用例。测试的最常见用例是检查函数、方法和其他可调用对象的返回值。在下面的示例中,我们有一个函数add(a, b),它接受两个参数并返回两个值的和。
示例-
def add(a, b):
return float(a + b)
我们可以编写上述代码的文档以更好地理解代码。添加文档后,它将如下所示。
def add(a, b):
"""计算并返回两个数字的总和。
用法示例:
》》> add(8.0, 2.0)
10.0
》》> add(14, 12.0)
26.0
"""
return float(a + b)
文档字符串包括使用add()函数的两个示例。有一个初始行>>,其具有具有两个数字参数的add(),第二行是期望的输出。
要运行doctest,我们可以使用以下命令。
>python -m doctest add.py
上述命令将不起作用,或不会在屏幕上显示输出。换句话说,doctest运行了所有测试用例,但找不到失败的测试。我们可以使用下面的命令查看程序的详细信息。
>python -m doctest -v add.py
输出-
1个项目没有测试:
加
1个项目中没有测试。
0个通过,0个失败。
测试通过。
上述命令使用了-v选项,它产生了详细输出。前两行显示了测试和预期结果。随后的行指示测试成功,并使用“ok”表示已通过目标测试。在此示例中,两个测试都通过了,如最后一个突出显示的行所示。
输入和预期输出在文档字符串中指定,doctest模块使用此信息来测试函数的输出。文档字符串被处理,生成的文本作为Python shell命令执行,并将输出与从文档字符串中提取的预期结果进行比较。
在下面的示例中,我们将编写** factorial()**函数,它将返回给定数字的阶乘。让我们理解以下示例。
示例-
#导入testmod以测试我们的函数
from doctest import testmod
#定义要测试的函数
def factorial(num):
'''
该函数以递归方式计算并
返回正数的阶乘。
定义输入和期望输出:
》》》 factorial(5)
120
》》》 factorial(6)
720
'''
if num <= 1:
return 1
return num * factorial(num-1)
#调用testmod函数
if __name__ == '__main__':
testmod(name ='factorial',verbose = True)
输出:
尝试:
factorial(5)
期望:
120
好的
尝试:
factorial(6)
期望:
720
好的
1项没有测试:
把
1个项目中通过了所有测试:
2个测试在add.factorial中
2个项目中的2个测试。
2个通过,0个失败。
测试通过。
现在我们测试失败了。如果更改逻辑会怎样呢?
#导入testmod以测试我们的函数
from doctest import testmod
#定义要测试的函数
def factorial(num):
'''
该函数以递归方式计算并
返回正数的阶乘。
定义输入和期望输出:
》》》 factorial(5)
120
》》》 factorial(6)
720
'''
if num <= 1:
return 1
return factorial(num-1)
#调用testmod函数
if __name__ == '__main__':
testmod(name ='factorial',verbose = True)
输出:
失败的示例:
阶乘(6)
期望:
720
得到:
1
1项没有测试:
把
**********************************************************************
1项没有通过测试:
在add。阶乘中的2个
2个项目中的2个测试。
0个通过,2个失败。
***测试失败*** 2个失败。
正如我们所看到的,它显示了失败的测试。如果我们将 verbose 设为 False,那么它将只返回失败的消息。
探索 doctest 的一些限制
与其他测试框架相比,doctest 的一个主要限制是缺乏类似于 pytest 中的 fixtures 或 unittest 中的 setup 和 teardown 方法的特性。如果需要设置和拆卸代码,则必须在每个受影响的 docstring 中编写。另一个选择是 unittest API,它提供了设置和拆卸选项。
doctest 的另一个限制是它将期望的输出与实际输出进行严格对比,需要进行精确匹配。如果有一个字符不匹配,测试将失败,使得测试某些 Python 对象变得具有挑战性。
让我们理解下面的 doctest 的限制。
示例 –
class Student:
def __init__(self, name, favorite_subjects):
self.name = name
self._favorite_subjects= set(favorite_subjects)
@property
def favorite_subjects(self):
"""Return the user's favorite subjects.
>>> peter = Student("Peter", {"English", "Math", "Science"})
>>> peter.favorite_subjects
{"English", "Math", "Science"}
"""
return self._favorite_subjects
上面的学生类具有名称和喜欢的科目变量。类初始化器将输入主题转换为 set 对象。我们从 favorite_subjects() 属性中获取学生的喜欢科目。正如我们所知,集合以随机顺序存储元素。因此,我们的测试用例大多数时候会失败。现在,运行以下命令以查看输出。
输出 –
$ python -m doctest -v add.py
Trying:
peter = Student("Peter", {"English", "Math", "Science"})
Expecting nothing
ok
Trying:
peter.favorite_subjects
Expecting:
{"English", "Math", "Science"}
**********************************************************************
File "C:\Users\User\Desktop\my_project\site_checker\add.py", line ?, in add.Student.favorite_subjects
Failed example:
peter.favorite_subjects
Exception raised:
Traceback (most recent call last):
File "C:\Python\lib\doctest.py", line 1329, in __run
exec(compile(example.source, filename, "single",
File "", line 1, in
peter.favorite_subjects
AttributeError: 'Student' object has no attribute 'favorite_subjects'
3 items had no tests:
add
add.Student
add.Student.__init__
**********************************************************************
1 items had failures:
1 of 2 in add.Student.favorite_subjects
2 tests in 4 items.
1 passed and 1 failed.
***Test Failed*** 1 failures.
在第一个测试中,实例化了 Student 类的对象,该对象没有任何预期输出。第二个输出检查期望输出与函数的实际输出是否一致。由于集合是无序集合,因此输出不同,导致测试失败。
为了解决这样的问题,我们可以在 doctest 中使用 sorted() 函数。让我们看看以下解决方案。
示例 –
class Student:
def __init__(self, name, favorite_subjects):
self.name = name
self._favorite_subjects= set(favorite_subjects)
@property
def favorite_subjects(self):
"""Return the user's favorite subjects.
>>> peter = Student("Peter", {"English", "Math", "Science"})
>>> sorted(peter.favorite_subjects)
["English", "Math", "Science"]
"""
return self._favorite_subjects
现在我们使用 sorted() 函数,它返回排序后的主题列表。我们还需要更新预期输出以得到所有测试通过。
另一个doctest的限制是需要能够使用多组输入参数和预期输出结果运行测试,这被称为参数化,并要求测试框架自动使用所有组合运行目标测试并评估它们是否都通过。虽然doctest没有内置支持参数化的功能,但我们仍然可以使用某些技巧实现使用单个测试函数来拥有多个测试用例的好处。参数化使您能够高效地创建大量的测试用例,并提高测试覆盖率,最终提高生产力。
示例 –
def even_numbers(num):
"""Return the even numbers in a list.
>>> args = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
>>> expected = [[2, 4], [6, 8], [10, 12]]
>>> for arg, expected in zip(args, expected):
... get_even_numbers(arg) == expected
True
True
True
"""
return [number for number in num if number % 2 == 0]
在代码中,我们创建了两个列表来存储 even_numbers() 函数的输入参数和预期输出。然后,循环利用 zip() 函数同时遍历这两个列表并在每次迭代中运行一个测试。测试将 even_numbers() 的实际输出与第二个列表中相应的预期输出进行比较。
命令行Doctest
到目前为止,我们已经学习了doctest的基础知识和从命令行运行测试的方法。最基本的使用方法是像上一节中所做的那样,将目标文件或模块作为参数。我们已经学习了 -v 和 –verbose 命令来获取 doctest 所有测试的详细报告以及汇总。除了这个命令行选项,doctest 还接受其他选项。
选项 | 描述 |
---|---|
-h. – –help | 显示 doctest 的命令行帮助。 |
-o 或 -option | 指定一个或多个 doctest 选项标志或指令,用于在运行测试时使用 |
-f, –fail-fast | 在第一次失败后停止运行您的 doctest 测试 |
在大多数情况下,我们将在命令行中使用doctest。在表中列出的选项中,最复杂的选项是 -o 或 –option,因为可以利用此选项使用广泛的选项标志列表。
结论
现在您已经熟悉编写既作文档又作测试用例的代码示例的过程。为了将您的示例执行为测试,您使用了Python标准库中的 doctest 模块。这个模块为您提供了一个简单的测试框架,易于学习,使您能够快速开始自动化测试过程。