跳转到主要内容

内置doctest模块的重写

项目描述

GithubActions CircleCI Appveyor Codecov Pypi PypiDownloads ReadTheDocs

https://i.imgur.com/u0tYYxM.png

Xdoctest - 执行文档测试。一个用于在文档字符串中执行测试的Python包!

什么是文档测试?它是你写在文档字符串中的示例代码!什么是文档字符串?它是一个用作注释的字符串!它们被附加到Python函数和类作为元数据。它们通常用于自动生成文档。为什么它很酷?因为你可以边编码边写测试!

测试很好。文档很好。示例很好。Doctests 代码复用低,你可以直接在代码文件中编写它们。这通常可以帮助你编写函数。在你的文件中写下如何构造最小示例输入(拥有创建这些输入的工具会有帮助) 。将那段代码复制到 IPython/Jupyter 中,并尝试你的实现。将你的最终代码复制到正文中。写下如何使用示例输入调用函数。如果你愿意,检查结果是否与预期结果相符(虽然断言和检查很棒,但仅展示如何运行代码的测试也比没有测试要好)。

def an_algorithm(data, config):
    """
    Example:
        >>> data = '([()[]])[{}([[]])]'
        >>> config = {'outer': sum, 'inner': ord}
        >>> an_algorithm(data, config)
        1411
    """
    # I wrote this function by first finding some interesting demodata
    # then I wrote the body in IPython and copied it back in.
    # Now I can re-use this test code I wrote in development as a test!
    # Covered Code is much easier to debug (we have a MWE)!
    result = config['outer'](map(config['inner'], data))
    return result

问题是什么?如何在你的 doctest 中运行代码?

Xdoctest 可以帮你找到并执行你的 doctests。只需运行 xdoctest <path-to-my-module> 即可。它连接到 pytest,使其在 CI 中运行变得容易。安装并运行 pytest --xdoctest

xdoctest 包是对 Python 内置的 doctest 模块的重写。它用 Python 的 ast 模块替换了旧的基于正则表达式的解析器。目标是使 doctests 更容易编写,配置更简单,并鼓励测试驱动开发模式。

阅读文档

https://xdoctest.readthedocs.io

Github

https://github.com/Erotemic/xdoctest

Pypi

https://pypi.ac.cn/project/xdoctest

PyCon 2020

Youtube 视频Google 幻灯片

快速入门

安装:从 PyPI

Xdoctest 以通用 wheel 的形式在 PyPI 上分发,可以在 Python 3.8+ 上通过 pip 安装(1.1.0 版本中移除了对 Python 2.7 和 3.4/3.5 的支持,1.2.0 版本中移除了对 3.6/3.7 的支持)。安装已在 CPython 和 PyPy 实现上进行了测试。

pip install xdoctest

PyPI 上的分发使用 GPG 公钥签名:D297D757。如果你足够关心并检查 gpg 签名(希望 pip 以后会这样做),你还应该验证这与 dev/public_gpg_key 的内容是否一致。

用法:运行你的 doctests

安装后,运行项目中的所有 doctests 的最快方法是

python -m xdoctest /path/to/your/pkg-or-module.py

或者如果你的模块已被 pip 安装或位于 PYTHONPATH 中,则运行

python -m xdoctest yourmodname

入门指南

使用 xdoctest 有两种方式:通过 pytest 或通过原生接口。原生接口不那么神秘和隐晦,但其目的是运行 doctests。另一个选项是使用广泛使用的 pytest 包。这允许你使用相同的命令运行单元测试和 doctests,并具有许多其他优势。

建议使用 pytest 进行自动测试(例如,在你的 CI 脚本中),但对于调试,可能更易于使用原生接口。

检查 xdoctest 是否适用于你的包

你可以通过 pip 安装它并运行 python -m xdoctest <pkg> all 来快速检查 xdoctest 是否可以无缝地应用于你的包,其中 <pkg> 是你的 Python 包/模块的路径(如果它已安装在你的 PYTHONPATH 中,则可以使用其名称)。

例如,你可以测试 xdoctest 是否适用于 networkxsklearn,如下所示: python -m xdoctest networkx all / python -m xdoctest sklearn all

使用 pytest 接口

当运行 pytest 时,xdoctest 会自动被发现,但默认情况下是禁用的。这是因为 xdoctest 需要替换内置的 doctest 插件。

要启用此插件,请使用 --xdoctest--xdoc 运行 pytest。这可以在命令行中指定,也可以添加到 pytest.initox.ini 文件中的 [pytest] 部分的 addopts 选项。

要运行特定的 doctest,xdoctest 会使用以下模式为这些 doctests 设置 pytest 节点名称: <path/to/file.py>::<callname>:<num>。例如,一个函数的 doctest 可能如下所示 mymod.py::funcname:0,而一个类方法的 doctest 可能如下所示: mymod.py::ClassName::method:0

使用原生接口。

除了 pytest 插件外,xdoctest 还有一个原生的 doctest 运行器。您可以使用与包一起安装的 xdoctest 命令行工具,并将其指向模块目录或特定文件。

您还可以使用 xdoctest.doctest_module(path) 方法,将其放置在任何模块的 __main__ 部分中,以便在调用模块作为 __main__ 时使用 xdoctest 原生运行器。

if __name__ == '__main__':
    import xdoctest
    xdoctest.doctest_module(__file__)

这设置了调用 xdoctest 命令行界面的能力。 python -m <modname> <command>

但是,通常更倾向于直接使用 xdoctest 可执行文件,并传递文件的路径或已安装模块的名称。在这种情况下,它将像这样调用:xdoctest -m <modname> <command>

使用这两种方法中的任何一种,您都可以在模块或包上本地调用 xdoctest,从而公开命令行界面。这两种方法都公开了命令行界面,允许您传递命令给 xdoctest。

  • 如果 <command>all,则在模块中执行每个启用的 doctest: python -m <modname> all

  • 如果 <command>list,则列出每个启用的 doctest 的名称。

  • 如果 <command>dump,则将所有 doctests 转换为适合单元测试的格式,并输出到标准输出(0.4.0 新增)。

  • 如果 <command>callname(函数或类和方法的名称),则执行该特定 doctest: python -m <modname> <callname>。注意:您可以通过这种方式执行禁用的 doctests 或函数,无需任何参数(零参数)。

例如,如果您创建了一个包含以下代码的模块 mymod.py

def func1():
    """
    Example:
        >>> assert func1() == 1
    """
    return 1

def func2(a):
    """
    Example:
        >>> assert func2(1) == 2
        >>> assert func2(2) == 3
    """
    return a + 1

您可以使用以下命令

  • 使用命令 xdoctest -m mymod list 列出具有 doctests 的所有函数的名称

  • 使用命令 xdoctest -m mymod all 运行所有具有 doctests 的函数

  • 使用命令 xdoctest -m mymod func1 仅运行 func1 的 doctest

  • 使用命令 xdoctest -m mymod func2 仅运行 func2 的 doctest

--help 传递给调用原生运行器的方式之一将产生类似于以下内容的输出,概述了其他可用选项

usage: xdoctest [-h] [--version] [-m MODNAME] [-c COMMAND] [--style {auto,google,freeform}] [--analysis {auto,static,dynamic}] [--durations DURATIONS] [--time]
                [--colored COLORED] [--nocolor] [--offset] [--report {none,cdiff,ndiff,udiff,only_first_failure}] [--options OPTIONS] [--global-exec GLOBAL_EXEC]
                [--verbose VERBOSE] [--quiet] [--silent]
                [arg ...]

Xdoctest 1.0.1 - on Python - 3.9.9 (main, Jun 10 2022, 17:45:11)
[GCC 11.2.0] - discover and run doctests within a python package

positional arguments:
  arg                   Ignored if optional arguments are specified, otherwise: Defaults --modname to arg.pop(0). Defaults --command to arg.pop(0). (default: None)

optional arguments:
  -h, --help            show this help message and exit
  --version             Display version info and quit (default: False)
  -m MODNAME, --modname MODNAME
                        Module name or path. If specified positional modules are ignored (default: None)
  -c COMMAND, --command COMMAND
                        A doctest name or a command (list|all|<callname>). Defaults to all (default: None)
  --style {auto,google,freeform}
                        Choose the style of doctests that will be parsed (default: auto)
  --analysis {auto,static,dynamic}
                        How doctests are collected (default: auto)
  --durations DURATIONS
                        Specify execution times for slowest N tests.N=0 will show times for all tests (default: None)
  --time                Same as if durations=0 (default: False)
  --colored COLORED     Enable or disable ANSI coloration in stdout (default: True)
  --nocolor             Disable ANSI coloration in stdout
  --offset              If True formatted source linenumbers will agree with their location in the source file. Otherwise they will be relative to the doctest itself. (default:
                        False)
  --report {none,cdiff,ndiff,udiff,only_first_failure}
                        Choose another output format for diffs on xdoctest failure (default: udiff)
  --options OPTIONS     Default directive flags for doctests (default: None)
  --global-exec GLOBAL_EXEC
                        Custom Python code to execute before every test (default: None)
  --verbose VERBOSE     Verbosity level. 0 is silent, 1 prints out test names, 2 additionally prints test stdout, 3 additionally prints test source (default: 3)
  --quiet               sets verbosity to 1
  --silent              sets verbosity to 0

零参数运行器

原生接口在 xdoctest 运行器中具有“零参数”模式。这允许您在命令行中运行您的模块中的函数,只要它们不接收参数。目的是快速访问您的代码中的函数(因为 xdoctest 正在占用 __main__ 块的空间)。

例如,您可能创建一个名为 mymod.py 的模块,包含以下代码

def myfunc():
    print('hello world')

if __name__ == '__main__':
    import xdoctest
    xdoctest.doctest_module(__file__)

尽管 myfunc 没有doctest,但它仍然可以通过命令 python -m mymod myfunc 来运行。

注意,尽管“零参数”函数可以通过此接口运行,但它们既不会被 python -m mymod all 运行,也不会被 python -m mymod list 列出。

但是,如果您经常这样做,您可能更喜欢使用 fire

增强功能

xdoctest 相对于 doctest 的主要增强功能包括

  1. 现在所有 doctest 行都可以以 >>> 前缀。开发者无需区分 PS1PS2 行。然而,旧式 doctests,其中 PS2 行以前缀 ... 为标志,仍然有效。

  2. 此外,多行字符串不需要任何前缀(但如果有前缀也是可以的)。

  3. 测试现在是按块执行的,而不是逐行执行,因此基于注释的指令(例如 # doctest: +SKIP)现在可以应用于整个块(通过将其放在上面的行中),而不仅仅是应用于单行(通过将其放在行尾)。

  4. 没有“期望”语句的测试将忽略任何 stdout / 最终评估值。这使得使用简单的 assert 语句在可能写入 stdout 的代码中执行检查变得容易。

  5. 如果测试有一个“期望”语句并以值和 stdout 结尾,两者都会进行检查,如果任一匹配,则测试通过。

  6. 现在可以通过单个“获取”语句检查多个连续的 print 语句的输出。(0.4.0 新功能)。

  7. 示例可以包括顶层 异步代码

请参阅 dev/_compare/demo_enhancements.py 中的代码,以了解这些增强功能的演示。这个演示显示了 xdoctest 可以工作但 doctest 无法工作的情况。截至版本 0.9.1,没有已知的向后不兼容的语法。如果您发现任何向后不兼容的情况,请提交问题。

示例

以下是一个演示新宽松(且向后兼容)语法的示例

def func():
    """
    # Old way
    >>> def func():
    ...     print('The old regex-based parser required specific formatting')
    >>> func()
    The old regex-based parser required specific formatting

    # New way
    >>> def func():
    >>>     print('The new ast-based parser lets you prefix all lines with >>>')
    >>> func()
    The new ast-based parser lets you prefix all lines with >>>
    """
def func():
    """
    # Old way
    >>> print('''
    ... It would be nice if we didnt have to deal with prefixes
    ... in multiline strings.
    ... '''.strip())
    It would be nice if we didnt have to deal with prefixes
    in multiline strings.

    # New way
    >>> print('''
        Multiline can now be written without prefixes.
        Editing them is much more natural.
        '''.strip())
    Multiline can now be written without prefixes.
    Editing them is much more natural.

    # This is ok too
    >>> print('''
    >>> Just prefix everything with >>> and the doctest should work
    >>> '''.strip())
    Just prefix everything with >>> and the doctest should work

    """

Xdontest 解析风格

目前有三种主要的 doctest 解析风格:googlefreeform,以及第三种风格:auto,这是一种混合风格。

可以通过 Xdoctest CLI 中的 --style 命令行参数或使用 pytest 时的 --xdoctest-style 来设置解析风格。

设置 --style=google(或在 pytest 中的 --xdoctest-style=google)启用 Google 风格解析。期望 Google “docblock” 中存在带有 Example:Doctest: 标签的 Google 风格 doctest。此块中的所有代码都作为单个 doctest 解析。

设置 --style=freeform(或在 pytest 中的 --xdoctest-style=freeform)可以启用自由格式解析。自由格式 doctest 是任何以 >>> 开头的连续行块。这是内置 doctest 模块的原始解析格式。每个块都作为其自身的测试列出。

默认情况下,Xdoctest 设置 --style=auto(或在 pytest 中的 --xdoctest-style=auto),这将把所有 google-style 块作为单个 doctest 提取出来,同时仍然将所有其他以 >>> 开头的代码作为自由格式 doctest。

关于 got/want 测试的说明

默认情况下,新的 got/want 测试器非常宽容;它忽略了空白符的差异,尝试对 Python 2/3 的 Unicode/bytes 差异进行标准化,ANSI 格式化,并且默认使用旧的 doctest ELLIPSIS 模糊匹配器。如果“got”文本在任何地方与“want”文本匹配,则测试通过。

目前,这种宽容性不像原始 doctest 模块中那样高度可配置。是否应该支持这种级别的配置还是一个悬而未决的问题。如果测试需要在 got/want 检查器中具有高度的具体性,那么可能最好使用 assert 语句。

向后兼容性

与原始 doctests 没有已知的语法不兼容性。这是基于在 boltons、ubelt、networkx、pytorch 和一套广泛的测试套件中运行实际生活中的示例而得出的结论。如果您发现任何不兼容性,请提出问题或提交合并/拉取请求。

尽管具有完整的语法向后兼容性,但出于设计考虑,存在一些运行时不兼容性。具体来说,Xdoctest 启用了一组不同的默认指令,使得“got”/“want”检查器更加宽容。因此,在基于“got”/“want”检查失败的测试在 xdoctest 中可能通过。因此,建议您在系统关键代码上依赖编码的 assert 语句。这也使得在您意识到 doctests 变得太长时将 xdoctest 转换为 unittest 变得容易得多。

最后的示例

XDoctest 是对自己很好的展示。在通过 pip 安装 xdoctest 之后,尝试在 xdoctest 上运行 xdoctest。

xdoctest xdoctest

如果您希望输出稍微简洁一些,尝试

xdoctest xdoctest --verbose=1

# or

xdoctest xdoctest --verbose=0

您还可以考虑通过 pytest 运行 xdoctests 测试

pytest $(python -c 'import xdoctest, pathlib; print(pathlib.Path(xdoctest.__file__).parent)') --xdoctest

如果您希望输出稍微详细一些,尝试

pytest -s --verbose --xdoctest-verbose=3 --xdoctest $(python -c 'import xdoctest, pathlib; print(pathlib.Path(xdoctest.__file__).parent)')

如果您运行了这些命令,屏幕上飞过的无数字符都是 doctests 可以做的事情的示例。

项目详情


发布历史 发布通知 | RSS 源

下载文件

下载适用于您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分发

xdoctest-1.2.0.tar.gz (204.8 kB 查看哈希值)

上传时间:

构建分发

xdoctest-1.2.0-py3-none-any.whl (151.2 kB 查看哈希值)

上传时间: Python 3

支持者