有没有办法用doctest和sphinx来测试和文档化命令行应用程序?

11 投票
2 回答
570 浏览
提问于 2025-04-18 01:12

我有一个Python模块,我正在用Sphinx写教程,还包括了一些doctests(文档测试)。

这个模块附带了一些辅助程序。

我想把这些辅助程序也放进文档里,并且希望doctest能检查当前程序版本和文档中的标准输出是否一致。

我想我可以用sh模块或者popen来检查某个程序的标准输出,但我希望这些技巧不要出现在文档里,否则非程序员的用户肯定会搞不懂。

有没有办法做到这一点呢?

2 个回答

0

正如Raymond Hettinger提到的,你应该创建一个函数(比如叫做shell),这个函数可以接收一个字符串,然后使用subprocess库来运行这个字符串。你还可以用contextlib.redirect_stdout来装饰和管理输出流,这样结果就可以进行测试了。

不过,在生成的HTML中,显示的还是相同的Python代码,而不是Shell代码。为了修复这个问题,我们使用了以下扩展的JavaScript代码(这个代码是基于copybutton.js的):

$(document).ready(function() {
    const NAME_CLASS = "n";
    document.querySelectorAll(`.highlight-pycon pre .${NAME_CLASS}`).forEach(function(nameElement) {
        if (nameElement.innerText !== "shell")
            return;

        const GENERIC_PROMPT_CLASS = "gp";
        const promptElement = nameElement.previousElementSibling;
        if (!promptElement.classList.contains(GENERIC_PROMPT_CLASS))
            return;

        const GENERIC_OUTPUT_CLASS = "go";
        const GENERIC_TRACEBACK_CLASS = "gt";
        let pythonCode = "";
        let pythonCodeNodes = [];
        let currentNode = nameElement;
        while (
            currentNode
            && !(currentNode.classList?.contains(GENERIC_OUTPUT_CLASS)
                || currentNode.classList?.contains(GENERIC_TRACEBACK_CLASS))
        ) {
            pythonCode += currentNode.textContent;
            pythonCodeNodes.push(currentNode);
            currentNode = currentNode.nextSibling;
        }

        const outputStartElement = currentNode;

        const match = pythonCode.match(/shell\("(?<command>[^"]*)".*\)/);
        if (!match)
            return;

        const command = match.groups["command"]
        const invisiblePartsRemovedCommand = command.replace(/\s?\[.*\]/, '')
        const shellCode = invisiblePartsRemovedCommand + "\n"

        promptElement.innerText = "$ ";
        pythonCodeNodes.forEach(node => node.remove());
        promptElement.parentNode.insertBefore(document.createTextNode(shellCode), outputStartElement);
    });
});
3

doctest 模块只检查可以在 Python 交互式提示符下运行的语句。

你可以通过 subprocess 模块从 Python 交互式提示符调用命令行工具:

# Create Helper Function
>>> import subprocess
>>> run_commandline = lambda cmd: subprocess.check_output(cmd, shell=True).decode()

# Doctestable command-line calls
>>> print(run_commandline('cal 7 2017'))
     July 2017
Su Mo Tu We Th Fr Sa
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31

>>> print(run_commandline('echo $BASH_VERSION'))
3.2.57(1)-release

可能有一些方法可以修改 doctest 或 sphinx 来更直接地实现你的需求,但这个技巧是按照 doctest、sphinx 和 subprocess 的官方用法来使用的(doctest 是用来重放在文档字符串中找到的交互式提示符会话,而 subprocess 是用来直接从 Python 运行命令行工具并捕获它们的输出)。

我想我可以使用 sh 模块或 popen 来检查某个程序的标准输出,但我更希望这些技巧不要出现在文档中,否则非程序员用户肯定会感到困惑。

有两个想法:首先,这些调用的细节大部分可以隐藏在一个辅助函数中,以减少干扰。其次,如果你需要从 Python 调用命令行程序,使用 popensubprocess 并不是技巧,因为这些工具就是专门用来从 Python 进行这些调用的。

撰写回答