doctest使用记录

起源

最近刚刚刷完了 Coursea 一门公开课,感觉收货很大,推荐下这门课,Learn to Program: Crafting Quality Code。

Learn to Program: Crafting Quality Code

这门课从名字就能看出来并不是为 Python 基础入门而准备的,相反这门课是为大家进一步提高自己的编程能力而开设的。在5周的课程中,重点在于如何写出清晰,可用,健壮的程序。

这篇博客不细细介绍这门课程,推荐大家自行学习,这里总结下我觉得收货最大的一个地方,认识了 doctest这个非常好用的 Python 测试架构。

所有的 code 基于 Python3。

使用 doctest 的背景

这里我简单介绍下 doctest 的使用背景。如果脱离了使用场景和程序的背景,独立地评价某个程序的优劣是非常偏颇的。

正常我们编写程序,都会先总结出某个需求,然后设计数据结构并构思算法,甚至可能画出 UML 图,流程图来总结自己的想法。

在这个过程中,最重要的就是抽象出每一个功能模块的输入和输出,不考虑具体的模块内部设计,输入输出是你在整体把握程序时最重要的参考。

doctest 正是利用这个过程进行代码测试的工作。所以要体会到 doctest 的先进性,首先,必须明确你的代码设计过程是符合上述流程的。当然上述流程是绝大多数程序员喜欢的过程,也是绝大多数场景下推荐的(肯定存在特例,比如你的主要工作是接手别人的代码,ORZ)。既然我们在设计每个程序模块的时候都会规定好输入和输出,何不用这些规定来做程序测试?甚至进一步,这些测试能不能用来展示程序的用法呢?

doctest

根据官方文档的介绍 doctest 的目的是:

The doctest module searches for pieces of text that look like interactive Python sessions, and then executes those sessions to verify that they work exactly as shown. There are several common ways to use doctest:

  • To check that a module’s docstrings are up-to-date by verifying that all interactive examples still work as documented.
  • To perform regression testing by verifying that interactive examples from a test file or a test object work as expected.
  • To write tutorial documentation for a package, liberally illustrated with input-output examples. Depending on whether the examples or the expository text are emphasized, this has the flavor of “literate testing” or “executable documentation”.

示例

Talk is Cheap, show me the code.

这里直接举 doctest 官方文档中的例子,方便大家理解。

"""
This is the "example" module.

The example module supplies one function, factorial().  For example,

>>> factorial(5)
120
"""

def factorial(n):
    """Return the factorial of n, an exact integer >= 0.

    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(30)
    265252859812191058636308480000000
    >>> factorial(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0

    Factorials of floats are OK, but the float must be an exact integer:
    >>> factorial(30.1)
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
    >>> factorial(30.0)
    265252859812191058636308480000000

    It must also not be ridiculously large:
    >>> factorial(1e100)
    Traceback (most recent call last):
        ...
    OverflowError: n too large
    """

    import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result


if __name__ == "__main__":
    import doctest
    doctest.testmod()

上面的 code 就是 Python 代码,设代码存在example.py文件中。

代码的功能就是返回所有正整数的阶乘。

现在就可以在 terminal 使用如下方法调用该文件:

python example.py

会发现没有任何异常输出,这表示代码在注释中所有的测试都通过,所以不显示输出。

如果觉得不直观,可以添加如下的参数,再运行:

>>>python example.py -v
Trying:
    factorial(5)
Expecting:
    120
ok
Trying:
    [factorial(n) for n in range(6)]
Expecting:
    [1, 1, 2, 6, 24, 120]
ok
Trying:
    factorial(30)
Expecting:
    265252859812191058636308480000000
ok
Trying:
    factorial(-1)
Expecting:
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0
ok
Trying:
    factorial(30.1)
Expecting:
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
ok
Trying:
    factorial(30.0)
Expecting:
    265252859812191058636308480000000
ok
Trying:
    factorial(1e100)
Expecting:
    Traceback (most recent call last):
        ...
    OverflowError: n too large
ok
2 items passed all tests:
   1 tests in __main__
   6 tests in __main__.factorial
7 tests in 2 items.
7 passed and 0 failed.
Test passed.

-v表示显示详细信息。最后的总与信息也是非常有用的,可以帮助具体判断。

当然,在这个例子中,更加值得探讨的是哪些例子是比较好的测试用例,针对这些测试用例有什么比较好的方法来检测。不过这是另外一个话题就不在这儿讨论了。当然你也可以看 doctest 自己的检测方法,如何 eat you own shit 的方法实现测试。具体网址 Test script for doctest.

讨论

测试文件

虽然,我很喜欢把测试文件写在源程序中,这样往往让程序比较臃肿,所以 doctest 还可以把测试语句写在文件中,比如这样:

The ``example`` module
======================

Using ``factorial``
-------------------

This is an example text file in reStructuredText format.  First import
``factorial`` from the ``example`` module:

    >>> from example import factorial

Now use it:

    >>> factorial(6)
    120

设文件存为 example.txt, 那么在 main 中的调用就是

import doctest
doctest.testfile("example.txt")

testfile()方法可以实现从文件中测试的目的。

总结

这是一篇简短的介绍,本身 doctest 包就非短小精悍,具体的细节可以参考官方文档. doctest — Test interactive Python examples

当然对于大型的项目,可能更加适合的使用的是 unittest 这样的测试框架而不是 doctest。这也是为什么在 MOOC 中又介绍 unittest 的原因。

Written on March 22, 2017