如何使用子进程有效地测试基于 readline 的 Python 程序?

How can I effectively test my readline-based Python program using subprocesses?

我有一个 Python 程序,在某些情况下,它应该提示用户输入文件名。但是,我想提供一个默认文件名,用户可以根据需要进行编辑。 这通常意味着他们需要按退格键删除当前文件名并将其替换为他们喜欢的文件名。

为此,我将 this answer 的 Python 3 改编为:

def rlinput(prompt, prefill=''):
    readline.set_startup_hook(lambda: readline.insert_text(prefill))
    try:
        return input(prompt)
    finally:
        readline.set_startup_hook()

new_filename = rlinput("What filename do you want?", "foo.txt")

当程序按预期 运行 交互时,这会按预期工作 - 在退格并输入新文件名后,new_filename 包含 bar.txt 或用户输入的任何文件名。

不过,我也想用单元测试来测试程序。通常,为了做到这一点,我 运行 程序作为一个子进程,这样我就可以将它输入到 stdin(并因此在用户使用它时对其进行测试)。我有一些单元测试代码(简化)如下所示:

p = Popen(['mypythonutility', 'some', 'arguments'], stdin=PIPE)
p.communicate('\b\b\bbar.txt')

我的意图是,这应该在提供的 foo.txt 上模拟用户 'backspacing',并改为输入 bar.txt

然而,这似乎没有达到预期的效果。相反,经过一些调试后,我的程序中的 new_filename 最终会出现等同于 \b\b\bbar.txt 的结果。我只期待 bar.txt.

我做错了什么?

从 Python 控制交互式子进程的适当方法是使用 pexpect 模块。此模块使子进程相信它是 运行 在交互式终端会话中,并让父进程准确确定将哪些击键发送给子进程。

Pexpect is a pure Python module for spawning child applications; controlling them; and responding to expected patterns in their output. Pexpect works like Don Libes’ Expect. Pexpect allows your script to spawn a child application and control it as if a human were typing commands.