各位朋友,晚上好!我是老码,今天咱们来聊聊Python里一个挺有意思的小工具,叫做 doctest
。 别看它名字有点儿学术,其实用起来特别接地气,能让你直接在文档字符串里写测试用例,就像给代码写小作文一样。
一、doctest
是个啥?
简单来说,doctest
是 Python 自带的一个模块,它允许你在文档字符串(docstring)里面嵌入测试用例。这些测试用例长得有点像 Python 交互式解释器的会话记录。 doctest
会读取这些会话记录,然后执行里面的代码,检查实际输出是否和文档字符串里写的一样。 如果不一样,就说明你的代码有问题了,赶紧去修bug吧!
二、为什么要用 doctest
?
你可能会问,现在测试框架那么多,unittest、pytest 哪个不比 doctest
强大? 为什么还要用它呢?
- 简单易用:
doctest
不需要额外的安装,Python 自带。而且语法简单,只要会写 Python 代码,就能写doctest
。 - 文档即测试:
doctest
把测试用例和文档放在一起,保证了文档的准确性。 写完代码,顺手写几个测试用例,就相当于给代码写了一份说明书,还带自动验证功能,一举两得。 - 学习成本低: 对于新手来说,
doctest
是一个很好的入门测试的工具。 不需要学习复杂的测试框架,就能体验到测试的乐趣。 - 示例代码验证: 如果你的文档里有很多示例代码,用
doctest
可以确保这些示例代码是真正可以运行的,而不是摆设。
三、doctest
怎么用?
咱们来点实际的,看看 doctest
到底怎么用。
1. 最简单的例子:
def add(a, b):
"""
这是一个加法函数。
>>> add(1, 2)
3
>>> add(-1, 1)
0
>>> add(0, 0)
0
"""
return a + b
if __name__ == "__main__":
import doctest
doctest.testmod()
这段代码定义了一个 add
函数,并在它的文档字符串里写了三个测试用例。 每个测试用例都以 >>>
开头,后面跟着 Python 代码,下一行是期望的输出结果。
运行这段代码,doctest
会自动执行文档字符串里的测试用例,如果一切正常,就没有任何输出。 如果有错误,doctest
会告诉你哪个测试用例失败了,以及期望的输出和实际的输出是什么。
2. 稍微复杂一点的例子:
def greet(name):
"""
打招呼函数。
>>> greet("Alice")
'Hello, Alice!'
>>> greet("Bob")
'Hello, Bob!'
>>> greet("")
'Hello, !'
"""
return "Hello, " + name + "!"
if __name__ == "__main__":
import doctest
doctest.testmod()
这个例子和上一个例子类似,只是函数返回的是字符串。
3. 处理异常:
有时候,我们需要测试函数是否会抛出异常。 doctest
也能处理这种情况。
def divide(a, b):
"""
除法函数。
>>> divide(10, 2)
5.0
>>> divide(5, 0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
"""
return a / b
if __name__ == "__main__":
import doctest
doctest.testmod()
注意,测试异常的时候,要写出完整的 traceback 信息,包括异常类型和异常信息。 ...
表示可以忽略中间的 traceback 信息。
4. 在模块级别使用 doctest
:
doctest
不仅可以在函数或类中使用,还可以在模块级别使用。 也就是说,你可以在模块的文档字符串里写测试用例。
"""
这是一个模块级别的 doctest 示例。
>>> 1 + 1
2
>>> "hello".upper()
'HELLO'
"""
def my_function():
"""
这是一个函数。
>>> my_function()
'Function called'
"""
return "Function called"
if __name__ == "__main__":
import doctest
doctest.testmod()
5. 使用 doctest.testfile
测试文件:
除了在文档字符串里写测试用例,还可以把测试用例写在单独的文件里,然后用 doctest.testfile
来运行这些测试用例。
创建一个名为 my_tests.txt
的文件,内容如下:
>>> 1 + 1
2
>>> "world".capitalize()
'World'
然后在 Python 代码里这样写:
import doctest
doctest.testfile("my_tests.txt")
运行这段代码,doctest
就会执行 my_tests.txt
里的测试用例。
四、doctest
的一些高级用法:
doctest
还有一些高级用法,可以让你更灵活地编写测试用例。
1. 使用 doctest
指令:
doctest
提供了一些指令,可以控制测试的行为。 这些指令写在测试用例的第一行,用 #!
开头。
# doctest: +SKIP
:跳过这个测试用例。# doctest: +ELLIPSIS
:忽略输出结果中的省略号...
。# doctest: +NORMALIZE_WHITESPACE
:忽略输出结果中的空白差异。# doctest: +IGNORE_EXCEPTION_DETAIL
:忽略异常的详细信息。
例如:
def my_list():
"""
返回一个列表。
>>> my_list() # doctest: +ELLIPSIS
[1, 2, ..., 10]
"""
return list(range(1, 11))
这个例子使用了 ELLIPSIS
指令,忽略了输出结果中列表的中间部分。 因为我们不想把整个列表都写出来,太长了。
2. 使用 doctest.DocTestSuite
:
doctest.DocTestSuite
可以把模块、类或函数的文档字符串转换成一个 unittest
测试套件。 这样就可以把 doctest
测试用例和 unittest
测试用例放在一起运行。
import unittest
import doctest
def my_function():
"""
一个简单的函数。
>>> my_function()
'Hello, world!'
"""
return "Hello, world!"
def load_tests(loader, tests, ignore):
tests.addTests(doctest.DocTestSuite(my_function))
return tests
class MyTestCase(unittest.TestCase):
def test_something(self):
self.assertEqual(1, 1)
这个例子展示了如何把 my_function
的 doctest
测试用例添加到 unittest
测试套件中。
五、doctest
的优缺点:
优点:
- 简单易用,学习成本低。
- 文档即测试,保证文档的准确性。
- 不需要额外的安装,Python 自带。
- 适合编写简单的测试用例和示例代码验证。
缺点:
- 功能相对简单,不如专业的测试框架强大。
- 不适合编写复杂的测试用例。
- 测试用例和代码耦合在一起,不利于代码的维护。
- 错误提示信息不够友好。
六、doctest
的适用场景:
- 编写简单的测试用例。
- 验证文档中的示例代码。
- 作为学习测试的入门工具。
- 小型项目或脚本。
七、doctest
和其他测试框架的比较:
特性 | doctest |
unittest |
pytest |
---|---|---|---|
易用性 | 非常简单 | 相对复杂 | 相对简单 |
功能 | 简单,主要用于文档测试 | 强大,支持各种测试场景 | 非常强大,支持各种测试场景和插件 |
文档集成 | 天然集成,测试用例在文档中 | 需要单独编写测试用例 | 需要单独编写测试用例 |
学习曲线 | 非常低 | 较高 | 较低 |
适用场景 | 简单测试,文档示例,小型项目 | 中大型项目,需要复杂测试逻辑 | 中大型项目,需要灵活的测试框架和插件支持 |
八、一些建议:
doctest
适合编写简单的测试用例,对于复杂的测试,还是建议使用专业的测试框架,比如unittest
或pytest
。doctest
的测试用例应该尽量简洁明了,避免在文档字符串里写太多的代码。doctest
的错误提示信息不够友好,需要仔细阅读 traceback 信息才能找到错误原因。doctest
可以作为学习测试的入门工具,让你快速了解测试的基本概念。- 在编写文档的时候,可以考虑使用
doctest
来验证文档中的示例代码,保证文档的准确性。
九、总结:
doctest
是 Python 里一个简单易用的测试工具,它可以让你直接在文档字符串里编写测试用例。 虽然它的功能相对简单,但对于编写简单的测试用例、验证文档中的示例代码、以及作为学习测试的入门工具来说,还是非常实用的。
好了,今天的分享就到这里,希望大家能喜欢上 doctest
这个小工具,让你的代码更加健壮,文档更加准确! 咱们下次再见!