有没有一种方法可以使用doctest和sphinx来测试和记录命令行应用程序?

11

我有一个Python模块,正在使用Sphinx编写包括doctest的教程。

这个模块配备了一些辅助程序。

我想在文档中包含这些帮助程序,并让doctest检查当前程序版本和文档之间的标准输出同步。

我想我可以使用sh模块或popen来检查给定程序的标准输出,但我希望这些技巧不会显示在文档中,否则非程序员用户肯定会迷失。

有什么方法可以实现这一点吗?


可能是 https://dev59.com/yWgv5IYBdhLWcg3wCsc8 的重复问题。 - Steve Piercy
我不认为这是一个重复的问题。另一个问题仅涉及自动文档生成。这个问题主要是关于一个更有趣的话题,使用doctest来测试命令行工具。 - Raymond Hettinger
2个回答

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的宣传API以完全设计它们使用的方式(doctest旨在回放在docstrings中找到的交互式提示会话,而subprocess旨在从python直接运行命令行工具并捕获其输出)。

我想我可以使用sh模块或popen检查给定程序的标准输出,但我更喜欢那些技巧不出现在文档中,否则非程序员用户肯定会迷失。

两个想法:首先,这些调用的详细信息可以大部分隐藏在辅助函数中,以最小化干扰。其次,如果您需要从Python调用命令行程序,那么使用popen或subprocess并不是一个技巧,因为这些是专门设计用于从Python进行这些调用的工具。

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);
    });
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接